[TOC]
# 1.2 Go 語言綜述
Go 語言的設計哲學是簡潔,在 Go 團隊的一次訪談中,Rob Pike 和 Ken Thompson 均有提到 Go 在誕生之初就選擇了一條與以往的編程語言所選擇的不同方法,僅嘗試提供編程過程中的必需品, 剔除掉紛繁復雜的語法糖和日漸增多的特性列表,在進入本書內容之前,我們先對這一語言做一次 相對完整的回顧。
## 1.1.1 基礎類型與值
### 基本類型
原始的數值類型包括:
```
bool,
uint, uint8, uint16, uint32, uint64,
int, int8, int16, int32, int64,
float32, float64
```
### 常量 const 與變量 var
常量`const`與變量`var`是編程語言里司空見慣的概念,Go 語言也不例外。 常量與變量的聲明允許類型推導,也允許明確指定值的類型。
```
const (
name = "val"
PI float64 = 3.1415926
)
var (
age = 18
)
```
變量除了使用`var`來進行聲明外,還可以在函數內部通過`:=`進行聲明。
## 1.1.2 程序的基本單元
Go 語言本身只有 25 個關鍵字,涵蓋了包管理、常量與變量、流程控制、函數調用、數據結構 和并發控制六個方面的語言特性。
### 包
package import
Go 語言以包為代碼組織的最小單位,不允許產生包與包之間的循環依賴。 包管理涉及兩個關鍵字,`package`負責聲明當前代碼所在的包的包名,`import`則負責導入 當前包所依賴的包。導入的形式包括絕對路徑和相對路徑,還能夠在導入時候對包指定別名,甚至將別名指定為下劃線,只導入所依賴包的`init`初始化函數。
```
package foo
import (
"go/types"
"golang.org/x/syscall"
"errors"
xerrors "golang.org/x/errors"
_ "os/signal"
)
```
特殊的,main 包只允許出現一次。
### 函數
#### 函數的聲明
```
func return
func foo(argc int, argv []string) float64 {
...
return 0
}
```
內建的打印函數
~~~
func print(args ...T)
func println(args ...T)
~~~
#### 延遲函數
defer
#### 主函數
#### 初始化函數
### 控制流
#### 條件控制
```
if else break continue switch case default fallthrough
```
#### 循環控制
```
for range
```
#### 跳躍
```
goto
```
## 1.1.3 數據容器與高級類型
### 字符串
```
`string`
```
### 切片與數組
```
[10]byte // 數組
[]byte // 切片
```
支持切片的基本操作
~~~
// 往切片末尾追加元素
func append(slice []T, elems ...T) []T
// 將 src 拷貝到 dst
func copy(dst, src []T) int
~~~
除了使用類型聲明外,還可以使用 make 和 new 來創建一個數據容器:
~~~
func make(t T, size ...IntegerT) T
func new(T) *T
~~~
內建 len 和 cap
~~~
func len(v T) int
func cap(v T) int
~~~
### 散列表
關鍵字 map 用于聲明一個散列表類型 map\[K\]V。
支持散列表操作的內建
~~~
func delete(m map[T]U, key T)
~~~
除此之外,make/new/len 也是可以用的
### 結構體
~~~
type struct interface
~~~
### 接口
### 類型別名
語言自身的類型別名包括: const ( true = 0 == 0 false = 0 != 0 iota = 0 ) 別名類型包括:byte rune
還可以使用 type 關鍵字來定義類型別名:
~~~
type New Old
~~~
### 指針與零值
~~~
var nil T
~~~
~~~
uintptr
~~~
## 1.1.4 并發與同步原語
### `go`塊
關鍵字 go 用于創建基本的并發單元。
```
go func() {
// 并發代碼塊
...
}()
```
被并發的函數將發生在一個獨立的 Goroutine 中,與產生 Goroutine 的代碼并發的被執行。
### Channel
Channel 主要有兩種形式:
1. **有緩存 Channel(buffered channel)**,使用`make(chan T, n)`創建
2. **無緩存 Channel(unbuffered channel)**,使用`make(chan T)`創建
其中`T`為 Channel 傳遞數據的類型,`n`為緩存的大小,這兩種 Channel 的讀寫操作都非常簡單:
```
// 創建有緩存 Channel
ch := make(chan interface{}, 10)
// 創建無緩存 Channel
ch := make(chan struct{})
// 發送
ch <- v
// 接受
v := <- ch
```
他們之間的本質區別在于其內存模型的差異,這種內存模型在 Channel 上體現為:
* 有緩存 Channel:`ch <- v`發生在`v <- ch`之前
* 有緩存 Channel:`close(ch)`發生在`v <- ch && v == isZero(v)`之前
* 無緩存 Channel:`v <- ch`發生在`ch <- v`之前
* 無緩存 Channel: 如果`len(ch) == C`,則從 Channel 中收到第 k 個值發生在 k+C 個值得發送完成之前
直觀上我們很好理解他們之間的差異: 對于有緩存 Channel 而言,內部有一個緩沖隊列,數據會優先進入緩沖隊列,而后才被消費, 即向通道發送數據`ch <- v`發生在從通道接受數據`v <- ch`之前; 對于無緩存 Channel 而言,內部沒有緩沖隊列,即向通道發送數據`ch <- v`一旦出現, 通道接受數據`v <- ch`會立即執行, 因此從通道接受數據`v <- ch`發生在向通道發送數據`ch <- v`之前。 我們隨后再根據實際實現來深入理解這一內存模型。
Go 語言還內建了`close()`函數來關閉一個 Channel:
```
close(ch)
```
但語言規范規定了一些要求:
* 關閉一個已關閉的 Channel 會導致 panic
* 向已經關閉的 Channel 發送數據會導致 panic
* 向已經關閉的 Channel 讀取數據不會導致 panic,但讀取的值為 Channel 緩存數據的零值,可以通過接受語句第二個返回值來檢查 Channel 是否關閉:
```
v, ok := <- ch
if !ok {
... // Channel 已經關閉
}
```
### Select
Select 語句伴隨 Channel 一起出現,常見的用法是:
```
select {
case ch <- v:
...
default:
...
}
```
或者:
```
select {
case v := <- ch:
...
default:
...
}
```
用于處理多個不同類型的`v`的發送與接收,并提供默認處理方式。
## 1.1.5 錯誤處理
Go 語言的錯誤處理被設計為值類型,錯誤以接口的形式在語言中進行表達:
```
type error interface {
Error() string
}
```
任何實現了`error`接口的類型均可以作為`error`類型。對于下面的`CustomErr`結構而言:
```
type CustomErr struct {
err error
}
func (c CustomErr) Error() string {
return fmt.Sprintf("err: %v", c.err)
}
```
由于其實現了`Error()`方法,于是可以以`error`類型返回給上層調用:
```
func foo() error {
return CustomErr{errors.New("this is an error")}
}
func main() {
err := foo()
if err != nil { panic(err) }
}
```
除了錯誤值以外,還可以使用`panic`與`recover`內建函數來進行錯誤的傳播:
```
func panic(v interface{})
func recover() interface{}
```
## 1.1.6 基礎工具
工具并不屬于語言本身,相反它卻在 Go 語言中有著舉足輕重的地位。
### go fmt
### go vet
### go mod
## 1.1.7 小結
我們使用了不算多的篇幅,基本上介紹完了整個 Go 語言的核心及使其成功運行的必備工具, 雖然有諸如 complex64、complex128 基礎類型和 complex、real、imag 等內建函數沒有 在這里進行介紹,但他們在除了科學計算等領域外極為少見,讀者在熟悉語言的其他部分后, 對這些為數不多的特性的掌握甚至都不是一個時間問題。
- 第一部分 :基礎篇
- 第1章 Go語言的前世今生
- 1.2 Go語言綜述
- 1.3 順序進程通訊
- 1.4 Plan9匯編語言
- 第2章 程序生命周期
- 2.1 從go命令談起
- 2.2 Go程序編譯流程
- 2.3 Go 程序啟動引導
- 2.4 主Goroutine的生與死
- 第3 章 語言核心
- 3.1 數組.切片與字符串
- 3.2 散列表
- 3.3 函數調用
- 3.4 延遲語句
- 3.5 恐慌與恢復內建函數
- 3.6 通信原語
- 3.7 接口
- 3.8 運行時類型系統
- 3.9 類型別名
- 3.10 進一步閱讀的參考文獻
- 第4章 錯誤
- 4.1 問題的演化
- 4.2 錯誤值檢查
- 4.3 錯誤格式與上下文
- 4.4 錯誤語義
- 4.5 錯誤處理的未來
- 4.6 進一步閱讀的參考文獻
- 第5章 同步模式
- 5.1 共享內存式同步模式
- 5.2 互斥鎖
- 5.3 原子操作
- 5.4 條件變量
- 5.5 同步組
- 5.6 緩存池
- 5.7 并發安全散列表
- 5.8 上下文
- 5.9 內存一致模型
- 5.10 進一步閱讀的文獻參考
- 第二部分 運行時篇
- 第6章 并發調度
- 6.1 隨機調度的基本概念
- 6.2 工作竊取式調度
- 6.3 MPG模型與并發調度單
- 6.4 調度循環
- 6.5 線程管理
- 6.6 信號處理機制
- 6.7 執行棧管理
- 6.8 協作與搶占
- 6.9 系統監控
- 6.10 網絡輪詢器
- 6.11 計時器
- 6.12 非均勻訪存下的調度模型
- 6.13 進一步閱讀的參考文獻
- 第7章 內存分配
- 7.1 設計原則
- 7.2 組件
- 7.3 初始化
- 7.4 大對象分配
- 7.5 小對象分配
- 7.6 微對象分配
- 7.7 頁分配器
- 7.8 內存統計
- 第8章 垃圾回收
- 8.1 垃圾回收的基本想法
- 8.2 寫屏幕技術
- 8.3 調步模型與強弱觸發邊界
- 8.4 掃描標記與標記輔助
- 8.5 免清掃式位圖技術
- 8.6 前進保障與終止檢測
- 8.7 安全點分析
- 8.8 分代假設與代際回收
- 8.9 請求假設與實務制導回收
- 8.10 終結器
- 8.11 過去,現在與未來
- 8.12 垃圾回收統一理論
- 8.13 進一步閱讀的參考文獻
- 第三部分 工具鏈篇
- 第9章 代碼分析
- 9.1 死鎖檢測
- 9.2 競爭檢測
- 9.3 性能追蹤
- 9.4 代碼測試
- 9.5 基準測試
- 9.6 運行時統計量
- 9.7 語言服務協議
- 第10章 依賴管理
- 10.1 依賴管理的難點
- 10.2 語義化版本管理
- 10.3 最小版本選擇算法
- 10.4 Vgo 與dep之爭
- 第12章 泛型
- 12.1 泛型設計的演進
- 12.2 基于合約的泛型
- 12.3 類型檢查技術
- 12.4 泛型的未來
- 12.5 進一步閱讀的的參考文獻
- 第13章 編譯技術
- 13.1 詞法與文法
- 13.2 中間表示
- 13.3 優化器
- 13.4 指針檢查器
- 13.5 逃逸分析
- 13.6 自舉
- 13.7 鏈接器
- 13.8 匯編器
- 13.9 調用規約
- 13.10 cgo與系統調用
- 結束語: Go去向何方?