這一篇其實是為了要補充上一篇「LeetCode Concurrency Go 語言詳解:Print Zero Even Odd」沒寫到的細節,但所要解釋的概念,是針對 mulit-channel 管理上可能會犯的錯誤,與 LeetCode 比較沒關係,只是我剛好在解這一題 LeetCode 時學到的,所以最後決定將這部分獨立成一篇文章介紹。
為什麼說是地雷?當程式在自己的電腦上正常,我會很容易以為自己是對的,而且這個現象與作業系統的排程細節有關,很難找一個明確的環境原因。
補充:我踩到的 select-case-default 地雷
你可以看到,我的解題程式碼 default 那段是這麼寫的:
1 | default: |
以上其實是被高人「指點」後的。原本是這麼寫:
1 | default: |
這樣在我的 MacBook 依然正常,但是拿到 The Go Playground 上面就掛了,你可以自己修改程式碼(那篇解題文章最後有程式連結),在 The Go Playground 上試看看會怎樣?
原因是,雖然 select-case-default 會隨機均勻的嘗試每一個 case-default,但是並不會主動把 CPU 控制權交出去,需要用 runtime.Gosched()
或 <-time.After(time.Microsecond)
把 CPU 讓出給其他 goroutine。否則,其他的 goroutine 將可能沒有機會動作。
C# 裡的 Application.DoEvents()
也是一樣的意思,讓別的事件有機會被觸發。
那為什麼我的 MacBook 正常跑完?難道是 CPU 使用數量限制嗎?我們來看看這兩個平台可用的邏輯處理器數量:
- The Go Playground: runtime.NumCPU=1
- MacBook Air 2018: runtime.NumCPU=4
好的,我的 MacBook 果然有比較多邏輯處理器可用,但也不足以說明這就是原因。
於是,我索性在筆電上的程式碼開頭加上一行 runtime.GOMAXPROCS(1)
限制此程式與 The Go Playground 一樣,只能用一個邏輯 CPU。結果,字出來是變慢了,好像古老的打字機那樣,但也是順利正確的跑完了,無法重現 The Go Playground 上發生的錯誤。所以這樣的 bug,真的很難在不同平台上重現,是很不容易發現的地雷。