這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
golang的Channel
Channel 是 golang 一個非常重要的概念,如果你是剛開始使用 golang 的開發人員,你可能還沒有真正接觸這一概念,本篇我們將分析 golang 的Channel
1. 引入
要講 Channel 就避不開 Goroutine -- 協程。閑話不說, 直接上個例子
Goroutine demo:
package mainimport ( "fmt" "time")func main() { origin := 1 go doSomeCompute(origin) time.Sleep(5 * time.Second)}func doSomeCompute(num int) { result := num * 2 fmt.Println(result) return}
簡單來說,例子中有一個 main 的協程,一個 doSomeCompute 的協程。還有個延時退出的方法等待計算協程計算結果。我們嘗試思考這個例子的一些問題:
a. 如何擷取 doSomeCompute 的計算結果?
b. 如何擷取 doSomeCompute 的執行狀態,當前是執行完了還是執行中?
如何解決這種問題呢?
Channel!
2. Channel
Channel怎麼處理上面的問題?我們直接上代碼:
舉例
package mainimport ( "fmt")func main() { origin := 1 //一個無緩衝Channel res := make(chan int) go doSomeCompute(origin, res) fmt.Println(<-res)}func doSomeCompute(num int, res chan int) { result := num * 2 res <- result}
例子中,Channel 充當起了協程間通訊的橋樑。Channel 可以傳遞到協程說明它是安全執行緒的,事實也是如此。Channel 可以理解為管道,協程 doSomeCompute 向 Channel 寫入結果, main 中讀取結果。注意,例子中讀取 Channel 的地方會阻塞直到拿到計算結果,這樣就解決了問題 a 和 b。
2. Channel 的方向性
上面的例子中,計算協程是負責計算並將計算結果寫入 Channel ,如果我們希望保證計算協程不會從 Channel 中讀取資料該怎麼處理?很簡單,看例子:
func doSomeCompute(num int, res chan<- int) { result := num * 2 res <- result}
這個參數的聲明 chan<- int 就表示該函數只能講資料寫入 Channel ,而不能從中讀取資料。後面的 int 表示 Channel 中資料的格式。同樣的, 只可以讀取資料的 Channel 可以聲明為 <-chan int 。而例子中不帶有方向聲明的 Channel 則既可以寫入也可以讀取。
3. 阻塞性質
Channel 的讀取和寫入操作在各自的協程內部都是阻塞的。比如例子中 fmt.Println(<-res) , 這一語句會阻塞直至計算協程將計算結果放入,可以讀出。也就是說,協程會阻塞直至從 res 中讀出資料。
注意,無緩衝的 Channel 的讀寫都是阻塞的,有緩衝的 Channel 可以一直向裡面寫資料,直到緩衝滿才會阻塞。讀取資料同理, 直至 Channel 為空白才阻塞。
用一個典型的例子來說明緩衝和非緩衝 Channel 的區別:
package mainimport "fmt"func doSomeCompute(ch chan int) { fmt.Println("deadlock test") <- ch}func main() { ch := make(chan int) ch <- 1 go doSomeCompute(ch)}
例子中,main 協程會向 ch 寫入資料, 這一過程是阻塞的,也就是說,doSomeCompute 協程無法執行,程式死結。輸出如下:
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main() C:/mygo/src/demo/blog.go:12 +0x73exit status 2
如果改成有緩衝的 Channel :
package mainimport ( "fmt" "time")func doSomeCompute(ch chan int) { fmt.Println("deadlock test") <- ch}func main() { ch := make(chan int, 1) ch <- 1 go doSomeCompute(ch) time.Sleep(1 * time.Second)}
有與有緩衝的 Channel 寫入後不阻塞(下一次寫入才會阻塞),程式會繼續執行。
4. Channel 的資料結構
Channel 在 golang 中實際上就是個資料結構。在 Golang 源碼中, Channel 的資料結構 Hchan 的定義如下:
struct Hchan{ uint32 qcount; // total data in the q uint32 dataqsiz; // size of the circular q uint16 elemsize; bool closed; uint8 elemalign; Alg* elemalg; // interface for element type uint32 sendx; // send index uint32 recvx; // receive index WaitQ recvq; // list of recv waiters WaitQ sendq; // list of send waiters Lock;};
時間倉促,這次對 Channel 介紹寫的有點簡單粗暴,下次再寫。