sync的waitgroup功能
使用多線程時,進行等待多線程執行完畢后,才可以結束函數,有兩個選擇
channel
waitgroup
首先使用channel
func add (n *int , isok chan bool){ for i :=0 ;i 1000 ; i ++ { *n = *n + 1 } isok - true } func main () { var ok = make(chan bool , 2) var i,u = 0,0 go add(i , ok) go add(i , ok) for - ok { u++ if u == 2 { break } } fmt.Println(i) }
但是,如果線程一旦增多,就會導致代碼冗余,增加負擔,可以使用sync.WaitGroup包
func add (n *int , wait *sync.WaitGroup) { for i := 0 ; i 1000 ; i ++ { *n = *n + 1 } defer wait.Done() } func main () { var wait sync.WaitGroup var i = 0 wait.Add(2) go add(i , wait) go add(i , wait) wait.Wait() fmt.Println(i) }
type Wait struct {//創建一個結構體 Num int//線程的個數 ok chan bool//線程與函數通信的管道 } func (t *Wait) Add (n int){//初始化線程個數 t.Num = n t.ok = make(chan bool , n) } func (t *Wait) Done (){//執行完一個線程之后,執行的函數,每執行完一個線程,調用函數,使用通信管道進行傳輸一個true t.ok - true } func (t *Wait) Wait () {//等待函數,每次管道中放入一個true,說明,執行完一個線程,t.Num--,如果等于0說明所有線程執行結束 for - t.ok { t.Num-- if t.Num == 0 { break } } }
補充:Golang的WaitGroup陷阱
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
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。