這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
#核心內容:
已關閉的channel再次讀取會出現什麼現象?
如何判斷channel關閉?
什麼是nil channel有什麼用?
先看看出問題的程式碼片段(抽象精簡):
func TestReadFromClosedChan(t *testing.T) {asChan := func(vs ...int) <-chan int {c := make(chan int)go func() {for _, v := range vs {c <- vtime.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)}close(c)}()return c}merge := func(a, b <-chan int) <-chan int {c := make(chan int)go func() {for {select {case v := <-a:c <- vcase v := <-b:c <- v}}}()return c}a := asChan(1, 3, 5, 7)b := asChan(2, 4, 6, 8)c := merge(a, b)for v := range c {fmt.Println(v)}}
目的很簡單,就是把a和b兩個channel合并到c當中,再通過range遍曆把c裡的元素全部列印出來。
**BUT!!**看起來如此簡單的一段代碼得到的結果卻出乎意料。像這樣:
12345678000...#以及無數的0...
看起來c合并了ab之後還多了一批無意義的0。原因在於:
closed channel是可以被消費者繼續讀取的,在讀完了有意義的資料之後,將讀到一堆空值。比如這裡的int類型就是0。
瞭解這個問題之後,想到第一感覺是對0進行判斷,如果發現收到一堆0則將其拋棄。但是有兩個問題:
- 實際上資料還是在管道中流動,會造成空迴圈,影響效能。
- 業務上可能存在真實有意義的空值,這個時候0不代表管道關閉。
好在go為<-chan
操作提供了兩個傳回值:
item,ok <- chan
其中第二個參數就是對channel狀態的描述,false表示channnel已經關閉。這就讓我們可以通過channel的狀態來控制對channel的讀取。
可是即便這樣再次對channel進行讀取還是會讀到0,不夠優雅。這個時候可以通過nil channel來解決。
思路就是把已關閉的channel置為nil,在讀取的時候則優先判斷channel是否為nil。代碼就不寫了。很簡單的實現。
golang對channel的設計,只能說功能強大方便不足吧。很容易就會碰上這樣的坑。