Golang中的sync包的WaitGroup操作
sync的waitgroup功能
WaitGroup
使用多線程時,進行等待多線程執(zhí)行完畢后,才可以結(jié)束函數(shù),有兩個選擇
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) }
仿sync.WaitGroup功能
type Wait struct {//創(chuàng)建一個結(jié)構(gòu)體 Num int//線程的個數(shù) ok chan bool//線程與函數(shù)通信的管道 } func (t *Wait) Add (n int){//初始化線程個數(shù) t.Num = n t.ok = make(chan bool , n) } func (t *Wait) Done (){//執(zhí)行完一個線程之后,執(zhí)行的函數(shù),每執(zhí)行完一個線程,調(diào)用函數(shù),使用通信管道進行傳輸一個true t.ok <- true } func (t *Wait) Wait () {//等待函數(shù),每次管道中放入一個true,說明,執(zhí)行完一個線程,t.Num--,如果等于0說明所有線程執(zhí)行結(jié)束 for <- t.ok { t.Num-- if t.Num == 0 { break } } }
補充:Golang的WaitGroup陷阱
sync.WaitGroup是并發(fā)環(huán)境中,一個相當常用的數(shù)據(jù)結(jié)構(gòu),用來等待所有協(xié)程的結(jié)束,在寫代碼的時候都是按著例子的樣子寫的,也沒用深究過它的使用。前幾日想著能不能在協(xié)程中執(zhí)行Add()函數(shù),答案是不能,這里介紹下。
陷阱在WaitGroup的3個函數(shù)的調(diào)用順序上。先回顧下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é)程結(jié)束后退出,看看下面代碼有沒有什么問題?
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") }
發(fā)現(xiàn)問題了嗎?如果沒有看下面的運行結(jié)果:main函數(shù)在子協(xié)程結(jié)束前就開始結(jié)束了。
father main: father and all chindren exit child [9] child [0] child [4] child [7] child [8]
陷阱分析
產(chǎn)生以上問題的原因在于,創(chuàng)建協(xié)程后在協(xié)程內(nèi)才執(zhí)行Add()函數(shù),而此時Wait()函數(shù)可能已經(jīng)在執(zhí)行,甚至Wait()函數(shù)在所有Add()執(zhí)行前就執(zhí)行了,Wait()執(zhí)行時立馬就滿足了WaitGroup的計數(shù)器為0,Wait結(jié)束,主程序退出,導致所有子協(xié)程還沒完全退出,main函數(shù)就結(jié)束了。
正確的做法
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ù),以此確保。
下面是修改后的程序,以及輸出結(jié)果。
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
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Go語言基礎(chǔ)語法之結(jié)構(gòu)體及方法詳解
結(jié)構(gòu)體類型可以用來保存不同類型的數(shù)據(jù),也可以通過方法的形式來聲明它的行為。本文將介紹go語言中的結(jié)構(gòu)體和方法,以及“繼承”的實現(xiàn)方法2021-09-09golang 實現(xiàn)Location跳轉(zhuǎn)方式
這篇文章主要介紹了golang 實現(xiàn)Location跳轉(zhuǎn)方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-05-05golang方法中receiver為指針與不為指針的區(qū)別詳析
這篇文章主要給大家介紹了關(guān)于golang方法中receiver為指針與不為指針區(qū)別的相關(guān)資料,其實最大的區(qū)別應該是指針傳遞的是對像的引用,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10go如何優(yōu)雅關(guān)閉Graceful?Shutdown服務
這篇文章主要為大家介紹了go優(yōu)雅關(guān)閉Graceful?Shutdown服務詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05