這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
註:該文原文為 Channel Axioms ,作者是 Dave Cheney,這是他的部落格地址
大部分的新的 Go 程式員能快速理解 channel 是作為一個 queue 的值和認同當 channel 是滿的或者是空的時候, 操作是阻塞的概念。
這篇文章探討了 channel 四個不太常見的特性:
- 給一個 nil channel 發送資料,造成永遠阻塞
- 從一個 nil channel 接收資料,造成永遠阻塞
- 給一個已經關閉的 channel 發送資料,引起 panic
- 從一個已經關閉的 channel 接收資料,立即返回一個零值
給一個 nil channel 發送資料,造成永遠阻塞
這第一個例子對於新來者是有點小驚奇的,它給一個 nil channel 發送資料,造成永遠阻塞。
以下這個程式將在第5行造成死結,因為未初始化的 channel 是 nil 的,其值是零
package mainfunc main() { var c chan string c <- "let's get started" // deadlock}
點擊這裡運行
從一個 nil channel 接收資料,造成永遠阻塞
類似的,從一個 nil channel 接收資料,會造成接受者永遠阻塞。
package mainimport "fmt"func main() { var c chan string fmt.Println(<-c) // deadlock}
點擊這裡運行
為什麼會發生這樣的情況?下面是一個可能的解釋
- channel 的 buffer 的大小不是型別宣告的一部分,因此它必須是 channel 的值的一部分
- 如果 channel 未被初始化,它的 buffer 的大小將是0
- 如果 channel 的 buffer 大小是0,那麼它將沒有 buffer
- 如果 channel 沒有 buffer,一個發送將會被阻塞,直到另外一個 goroutine 為接收做好了準備
- 如果 channel 是 nil 的,並且接收者和寄件者沒有任何互動,他們都會阻塞然後在各自的 channel 中等待以及不再被解除阻塞狀態
給一個已經關閉的 channel 發送資料,引起 panic
以下程式將有可能 panic,因為在它的兄弟姐妹有時間完成發送他們的值之前,這第一個 goroutine 在達到10的時候將關閉 channel。
package mainimport "fmt"func main() { var c = make(chan int, 100) for i := 0; i < 10; i++ { go func() { for j := 0; j < 10; j++ { c <- j } close(c) }() } for i := range c { fmt.Println(i) }}
點擊這裡運行
因此為什麼沒有一個 close() 版本能讓你檢測 channel 是否關閉?
if !isClosed(c) { // c isn't closed, send the value c <- v}
但是這個函數有一個內在的競爭,某個人可能在我們檢查完 isClosed(c) 之後,但是代碼擷取 c <- v 之前關閉這個 channel。
處理這個問題的方法在被串連在該文章底部的 2nd article 被討論。
從一個已經關閉的 channel 接收資料,立即返回一個零值
這最後一個樣本與前一個是相反的,一旦一個 channel 被關閉,它的所有的值都會從 buffer 中流失,channel 將立即返回0值。
package mainimport "fmt"func main() { c := make(chan int, 3) c <- 1 c <- 2 c <- 3 close(c) for i := 0; i < 4; i++ { fmt.Printf("%d ", <-c) // prints 1 2 3 0 }}
點擊這裡運行
針對這個問題的正確的解決辦法是使用 range 迴圈處理:
for v := range c { // do something with v}for v, ok := <- c; ok ; v, ok = <- c { // do something with v}
這兩個語句在函數中是相等的,展示 range 是做什麼。
擴充閱讀
- Concurrency is not Parallelism by Rob Pike
- Go Concurrency Patterns: Pipelines and cancellation
- Curious Channels