Go Channel 該注意的事項

Chung-chun Lo
Skyler Record
Published in
4 min readDec 21, 2020

在寫 Go 的時候 Channel 應該是常會被使用,但在我一開始使用 Channel 的時候有個疑惑就是為什麼 UnBuffer Channel 一定要在 Goroutine 下才能正常使用呢?Buffer 跟 UnBuffer 有什麼差?

Buffer/UnBuffer

Channel 的宣告上如果在 Make 的時候沒有指定 Buffer 數量的話就是預設 UnBuffer 簡來說就像這樣

messages := make(chan string,2) # Buffer為2
messages := make(chan string) # UnBuffer

UnBuffer Channel 就如同名稱一樣只要有一筆數據塞進去後就滿了,剩下的資料都塞不進去,除非 Channel 內的資料被讀取出來。

Buffer Channel 則相反只要塞入 Channel 的數據筆數沒超過 Buffer 的數量 Channel 就可繼續塞數據進去直到 Buffer 滿了

buffered1
buffered1

其實很明顯的可以看到 UnBuffer Channel 需要等到 Chan 裡面的數值都讀完main 才會結束,且 UnBuffer Channel 需要在 Goroutine 下才可正常運作但 Buffer Channel 卻沒有這些限制除非 Buffer 被塞滿了。

UnBuffer Channel 一定要在 Goroutine 下才能正常使用?

先前有提到 UnBuffer Channel 要在 Goroutine 下才能正常使用,但真的是這樣嗎?

這段 Code 看起來沒什麼問題,有了 Chan 的 input/output 確保它不會被 deadlock ,那執行起來會是怎麼樣呢?

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.StartWorker()
/tmp/sandbox711829247/prog.go:21 +0xf4
main.main()
/tmp/sandbox711829247/prog.go:8 +0x25

goroutine 18 [chan send]:
main.Worker(0xc00009e000, 0xc00009e060)
/tmp/sandbox711829247/prog.go:36 +0x4d
created by main.StartWorker
/tmp/sandbox711829247/prog.go:15 +0xae

原來我們忘了把第八行的地方移到 Goroutine 下執行了,既然這樣我們把第八行改一下

[0 16 18 2 4 6 8 10 12 14]

改完之後終於正常了,那如果我在把它改回來然後把 totalJobs 數量降成 4 並且在程式的開頭印一下我 CPU 的數量

fmt.Println("NumCPU : ", runtime.NumCPU())

這時候的結果會變成

4
[0 2 4 6]

發現了嗎是可以正常運作的,其實主要的原因是在於我的 CPU 數量只有四個所以一開始我建立了四個 Worker,然後準備塞十個數值進 Channel 塞完前面四個數值的時候都還算順利,因為四個 Worker 一人拿走了一個但在塞第五個的時候問題就來了。

Worker 的數值會塞回去身為 Unbuffer Channel 的 Output,況且 Output 的讀取是在 main function 做完 Input 數值塞入 Channel 之後才會開始進行。

這時會變成 Worker 無法將結果塞入 Output ,而 Input 也無法繼續塞數值給 Worker 自然就造成了deadlock。

使用 Goroutine 是為了避免在塞 Input 的階段卡住讓 Output 無法讀取,另外的取代方式就像剛剛提的使用 Buffer 大於 totalJobs 的 Channel 就行摟。

想試看看上面範例的人可以去 Go Playground 玩看看。

--

--