本文由go語言教程欄目給大家介紹關于Golang的WaitGroup陷阱,希望對需要的朋友有所幫助!
sync.WaitGroup
是并發(fā)環(huán)境中,一個相當常用的數(shù)據結構,用來等待所有協(xié)程的結束,在寫代碼的時候都是按著例子的樣子寫的,也沒用深究過它的使用。前幾日想著能不能在協(xié)程中執(zhí)行Add()
函數(shù),答案是不能,這里介紹下。
陷阱在WaitGroup的3個函數(shù)的調用順序上。先回顧下3個函數(shù)的功能:
Add(delta int)
:給計數(shù)器增加delta,比如啟動1個協(xié)程就增加1。Done()
:協(xié)程退出前執(zhí)行,把計數(shù)器減1。Wait()
:阻塞等待計數(shù)器為0。
考一考
下面的程序是創(chuàng)建了協(xié)程father,然后father協(xié)程創(chuàng)建了10個子協(xié)程,main函數(shù)等待所有協(xié)程結束后退出,看看下面代碼有沒有什么問題?
package main import ( "fmt" "sync" ) func father(wg *sync.WaitGroup) { wg.Add(1) defer wg.Done() fmt.Printf("fathern") 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") }
發(fā)現(xiàn)問題了嗎?如果沒有看下面的運行結果:main函數(shù)在子協(xié)程結束前就開始結束了。
father main: father and all chindren exit child [9] child [0] child [4] child [7] child [8]
陷阱分析
產生以上問題的原因在于,創(chuàng)建協(xié)程后在協(xié)程內才執(zhí)行Add()
函數(shù),而此時Wait()
函數(shù)可能已經在執(zhí)行,甚至Wait()
函數(shù)在所有Add()
執(zhí)行前就執(zhí)行了,Wait()
執(zhí)行時立馬就滿足了WaitGroup的計數(shù)器為0,Wait結束,主程序退出,導致所有子協(xié)程還沒完全退出,main函數(shù)就結束了。
正確的做法
Add函數(shù)一定要在Wait函數(shù)執(zhí)行前執(zhí)行,這在Add函數(shù)的文檔中就提示了: Note that calls with a positive delta that occur when the counter is zero must happen before a Wait.。
如何確保Add函數(shù)一定在Wait函數(shù)前執(zhí)行呢?在協(xié)程情況下,我們不能預知協(xié)程中代碼執(zhí)行的時間是否早于Wait函數(shù)的執(zhí)行時間,但是,我們可以在分配協(xié)程前就執(zhí)行Add函數(shù),然后再執(zhí)行Wait函數(shù),以此確保。
下面是修改后的程序,以及輸出結果。
package main import ( "fmt" "sync" ) func father(wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("fathern") 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