引言
Goroutine 和 Channel 是 Go 語言並發編程的兩大基石。Goroutine 用於執行並發任務,Channel 用於 goroutine 之間的同步、通信。
在Golang的並發哲學裡,有一句非常著名的話:
Do not communicate by sharing memory; instead, share memory by communicating.
意思是:不要通過共享內存來通信,而要通過通信來實現內存共享,它依賴CSP(Communication Sequence Process) 模型,簡稱通信順序進程。
Go提倡使用通信的方法代替共享內存,當一個Goroutine需要和其他Goroutine資源共享時,Channel就會在他們之間架起一座橋樑,並提供確保安全同步的機制。
Channel本質上還是一個隊列,遵循FIFO(First In-First Out)原則,
創建通道
創建通道需要用到關鍵字 make ,格式如下:
通道實例 := make(chan 數據類型)
- 數據類型:通道內傳輸的元素類型。
- 通道實例:通過make創建的通道句柄。
使用通道
通道創建後,就可以使用通道進行發送和接收操作。
寫入
通道的寫入使用特殊的操作符<-,將數據通過通道發送的格式為:
通道變量 <- 值
(可以簡單理解為箭頭方向為傳遞的值最終去向)
// 創建一個空接口通道
ch := make(chan interface{})
// 將0放入通道中
ch <- 0
// 將hello字符串放入通道中
ch <- "hello"
讀取
通道的讀取同樣使用<-操作符,通道接收有如下特性:
- 通道的收發操作在不同的兩個 goroutine 間進行。
- 由於通道的數據在沒有接收方處理時,數據發送方會持續阻塞,因此通道的接收必定在另外一個 goroutine 中進行。
- 接收將持續阻塞直到發送方發送數據。
通道的數據接收一共有以下 4 種寫法:
1) 阻塞式接收
阻塞模式接收數據時,接收值只有一個,格式如下:
data := <-ch
執行該語句時程序將會阻塞,直到接收到數據並賦值給 data 變量。
2) 非阻塞接收數據
使用非阻塞方式從通道接收數據時,語句不會發生阻塞,格式如下:
data, ok := <-ch
data:表示接收到的數據。未接收到數據時,data 為通道類型的零值。
ok:表示是否接收到數據。
特點:非阻塞的通道接收方法可能造成高的 CPU 占用,不建議這麼使用。
3) 忽略接收的數據
忽略從通道返回的任何數據,格式如下:
<-ch
特點:該方法也是阻塞的,必須等到通道返回了程序才會繼續往下走。
4) 循環接收
通道的數據接收可以借用 for range 語句進行多個元素的接收操作,格式如下:
for data := range ch {
// do sth.
}
通道 ch 是可以進行遍歷的,遍歷的結果就是接收到的數據。數據類型就是通道的數據類型。通過 for 遍歷獲得的變量只有一個,即上面例子中的 data。
只讀/只寫通道
一般來說,通道都是雙向的,即數據可以進入和輸出。但是,為了符合某些特殊業務場景,官方還提供了只支持讀(Read Only)或只支持寫(Write Only)的通道,格式如下:
//定義只讀通道
ch_r := <-chan interface{}
//定義只寫通道
ch_w := <-chan interface{}
有緩衝通道
Go語言中有緩衝的通道(buffered channel)是一種在被接收前能存儲一個或者多個值的通道。這種類型的通道並不強制要求 goroutine 之間必須同時完成發送和接收。通道會阻塞發送和接收動作的條件也會不同。只有在通道中沒有要接收的值時,接收動作才會阻塞。只有在通道沒有可用緩衝區容納被發送的值時,發送動作才會阻塞。
有緩衝通道的定義方式如下:
通道實例 := make(chan 通道類型, 緩衝大小)
- 通道類型:和無緩衝通道用法一致,影響通道發送和接收的數據類型。
- 緩衝大小:決定通道最多可以保存的元素數量。
- 通道實例:被創建出的通道實例。
下面我借用以下的圖片來說明下這個通道原理
- 左邊的goroutine不斷的往通道中塞數據
- 右邊的goroutine不斷的從通道中拿數據
- 一個讀/一個寫,同步作業
- 左邊的goroutine全部數據已經放完了,但此時通道中還剩餘數據,所有右邊的goroutine還在工作
無緩衝通道
Go語言中無緩衝的通道(unbuffered channel)是指在接收前沒有能力保存任何值的通道。這種類型的通道要求發送 goroutine 和接收 goroutine 同時準備好,才能完成發送和接收操作。
無緩衝通道的定義方式如下:
通道實例 := make(chan 通道類型)
- 通道類型:和無緩衝通道用法一致,影響通道發送和接收的數據類型。
- 緩衝大小:0
- 通道實例:被創建出的通道實例。
為了講得更清楚一些,我也找了一張額外的圖來說明:
在第 1 步,兩個 goroutine 都到達通道,但哪個都沒有開始執行發送或者接收。在第 2 步,左側的 goroutine 將它的手伸進了通道,這模擬了向通道發送數據的行為。這時,這個 goroutine 會在通道中被鎖住,直到交換完成。在第 3 步,右側的 goroutine 將它的手放入通道,這模擬了從通道里接收數據。這個 goroutine 一樣也會在通道中被鎖住,直到交換完成。在第 4 步和第 5 步,進行交換,並最終在第 6 步,兩個 goroutine 都將它們的手從通道里拿出來,這模擬了被鎖住的 goroutine 得到釋放。兩個 goroutine 現在都可以去做別的事情了。
總結
GoLang的通道是支撐並發系統穩定高效運行的重要工具,只有充分了解了它,才能在業務開發和問題排查中找到最關鍵的方案。知己知彼,百戰不殆。