介紹
眾所周知,如果我們使用 go 關鍵字進行協程的操作時,如果 主協程 沒有等待其他協程的話,直接結束會導致 任務無法完全執行。
範例如下:
1 | func main(){ |
一般來說,最暴力解的方式就是 讓主協程睡一會,等待子協程完成再來去完成主協程的任務。
1 | time.Sleep(3 * time.Second) |
但是這樣做非常沒有效率,且 sleep 的期間子協程可能早就已經結束了。
解決方法
- 使用
sync.Group - 使用
channel
使用 sync.WaitGroup
1 | func (wg *WaitGroup) Add(delta int) |
sync.WaitGroup 可以提供一個等待子協程完成的功能,主要配合使用 WaitGroup.Add(1)、WaitGroup.Wait()、WaitGroup.Done()。
WaitGroup.Add(1):sync.WaitGroup內有計數器,新增需要等待協成的數量。WaitGroup.Done():已經完成的程協執行後,可以減少sync.WaitGroup當前等待的數量。WaitGroup.Wait():用來阻塞當前協程,等待子協程,直到計數器歸零才會繼續執行。
使用方式
1 | func waitByWaitGroup(n int) { |
注意事項
WaitGroup.Add(num)使用時,num不能新增負數,不然會報錯panic: sync: negative WaitGroup counter。sync.WaitGroup在使用時是一個實例而不是指標,所以在函數裡需要記得以指標方式傳遞才不會產生deadlock。
使用 channel
使用迴圈的方式同步等待 channel 取出值來,邏輯與 sync.WaitGroup 相似。
1 | func waitByChannelCount(n int) { |
Benchmark
可以發現使用 sync.WaitGroup 速度約 channel 的兩倍,簡單又好用。
1 | $ go test -v -bench=. -run=none -benchmem . |
結論
其實要去等待子協程還是使用 sync.WaitGroup 程式碼看起來才會比較單純且高效,但是如果需要多個不同 channel 同時進行處理,再來考慮要不要使用 channel 配合 for + select 比較好。
本篇文章的程式碼範例。
參考資料
- https://learnku.com/articles/35130
- https://blog.wu-boy.com/2019/05/handle-multiple-channel-in-15-minutes/