注:該文原文為 Channel Axioms ,作者是 Dave Cheney,這是他的部落格地址
大部分的新的 Go 程式設計師能快速理解 channel 是作為一個 queue 的值和認同當 channel 是滿的或者是空的時候, 操作是阻塞的概念。
這篇文章探討了 channel 四個不太常見的特性:
- 給一個 nil channel 傳送資料,造成永遠阻塞
- 從一個 nil channel 接收資料,造成永遠阻塞
- 給一個已經關閉的 channel 傳送資料,引起 panic
- 從一個已經關閉的 channel 接收資料,立即返回一個零值
給一個 nil channel 傳送資料,造成永遠阻塞
這第一個例子對於新來者是有點小驚奇的,它給一個 nil channel 傳送資料,造成永遠阻塞。
以下這個程式將在第5行造成死鎖,因為未初始化的 channel 是 nil 的,其值是零
package main
func main() {
var c chan string
c <- "let`s get started" // deadlock
}
點選這裡執行
從一個 nil channel 接收資料,造成永遠阻塞
類似的,從一個 nil channel 接收資料,會造成接受者永遠阻塞。
package main
import "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 main
import "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 main
import "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
是做什麼。