Golang的sync.WaitGroup陷阱
阿新 • • 發佈:2019-01-05
sync.WaitGroup
是併發環境中,一個相當常用的資料結構,用來等待所有協程的結束,在寫程式碼的時候都是按著例子的樣子寫的,也沒用深究過它的使用。前幾日想著能不能在協程中執行Add()
函式,答案是不能,這裡介紹下。
陷阱在WaitGroup的3個函式的呼叫順序上。先回顧下3個函式的功能:
Add(delta int)
:給計數器增加delta,比如啟動1個協程就增加1。Done()
:協程退出前執行,把計數器減1。Wait()
:阻塞等待計數器為0。
考一考
下面的程式是建立了協程father,然後father協程建立了10個子協程,main函式等待所有協程結束後退出,看看下面程式碼有沒有什麼問題?
package main
import (
"fmt"
"sync"
)
func father(wg *sync.WaitGroup) {
wg.Add(1)
defer wg.Done()
fmt.Printf("father\n")
for i := 0; i < 10; i++ {
go child(wg, i)
}
}
func child(wg *sync.WaitGroup, id int) {
wg.Add(1)
defer wg.Done()
fmt.Printf("child [%d]\n", id)
}
func main() {
var wg sync.WaitGroup
go father(&wg)
wg.Wait()
log.Printf("main: father and all chindren exit")
}
發現問題了嗎?如果沒有看下面的執行結果:main函式在子協程結束前就開始結束了。
father
main: father and all chindren exit
child [9]
child [0]
child [4]
child [7]
child [8]
陷阱分析
產生以上問題的原因在於,建立協程後在協程內才執行Add()
函式,而此時Wait()
函式可能
Wait()
函式在所有Add()
執行前就執行了,Wait()
執行時立馬就滿足了WaitGroup的計數器為0,Wait結束,主程式退出,導致所有子協程還沒完全退出,main函式就結束了。
正確的做法
Add函式一定要在Wait函式執行前執行,這在Add函式的文件中就提示了: Note that calls with a positive delta that occur when the counter is zero must happen before a Wait.。
如何確保Add函式一定在Wait函式前執行呢?在協程情況下,我們不能預知協程中程式碼執行的時間是否早於Wait函式的執行時間,但是,我們可以在分配協程前就執行Add函式,然後再執行Wait函式,以此確保。
下面是修改後的程式,以及輸出結果。
package main
import (
"fmt"
"sync"
)
func father(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("father\n")
for i := 0; i < 10; i++ {
wg.Add(1)
go child(wg, i)
}
}
func child(wg *sync.WaitGroup, id int) {
defer wg.Done()
fmt.Printf("child [%d]\n", id)
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go father(&wg)
wg.Wait()
fmt.Println("main: father and all chindren exit")
}
father
child [9]
child [7]
child [8]
child [1]
child [4]
child [5]
child [2]
child [6]
child [0]
child [3]
main: father and all chindren exit
- 如果這篇文章對你有幫助,不妨關注下我的Github,有文章會收到通知。
- 本文作者:大彬
- 如果喜歡本文,隨意轉載,但請保留此原文連結:http://lessisbetter.site/2018/10/29/Golang-trap-of-waitgroup/