解決Golang 中使用WaitGroup的那點(diǎn)坑
sync.WaitGroup對(duì)于Golang開發(fā)者來說并不陌生,其經(jīng)常作為多協(xié)程之間同步的一種機(jī)制。用好它勢(shì)必會(huì)讓你事半功倍,但是一旦錯(cuò)用將引發(fā)問題。
關(guān)于WaitGroup的使用網(wǎng)上有很多例子,在此就不做介紹了,我想說的是我在項(xiàng)目中使用WaitGroup遇到的坑。
在項(xiàng)目中,因?yàn)榉?wù)器有同步需求, 所以直接使用了WaitGroup,但是未考慮使用場(chǎng)景,結(jié)果在項(xiàng)目上線之后,高峰期的時(shí)候客戶端經(jīng)常出現(xiàn)卡頓,經(jīng)過多方查找,才發(fā)現(xiàn)如果使用WaitGroup的時(shí)候,未啟動(dòng)單獨(dú)的goroutine,那么極有可能造成主線程的阻塞
所以我做了下面的測(cè)試(測(cè)試中,我把WaitGroup置于協(xié)程內(nèi)):
import ( "fmt" "sync" "time" ) func main() { fmt.Println("main-1") testW() fmt.Println("main-2") time.Sleep(time.Duration(15) * time.Second) } func testW() { fmt.Println("testW-1") go func() { var wg sync.WaitGroup fmt.Println("testW-2") testW1(&wg) fmt.Println("testW-5") wg.Wait() fmt.Println("testW-6") }() } func testW1(wg *sync.WaitGroup) { wg.Add(1) fmt.Println("testW-3") time.AfterFunc(time.Second*5, func() { wg.Done() }) fmt.Println("testW-4") }
輸出為:
main-1
testchan-1
main-2
testchan-2
testchan-3
testchan-4
testchan-5
// 過5秒
testchan-6
總結(jié):
將WaitGroup用于goroutine內(nèi),不會(huì)導(dǎo)致主線程的阻塞,同樣可以實(shí)現(xiàn)同步的效果。
補(bǔ)充:WaitGroup的簡(jiǎn)單用法(等待組)
你品一下人家這名字,等待組。等待什么,等待goroutine完成啊。有些時(shí)候,我們啟動(dòng)多個(gè)goroutine去執(zhí)行任務(wù),我舉個(gè)例子
listip := []string{"10.0.9.11","10.0.9.22","10.0.9.33"} for _, ip := range(listip) { //假設(shè)我們執(zhí)行一個(gè)ping ip 的邏輯 go PingIPWork(ip) }
我這里執(zhí)行了一個(gè)多ip去ping的邏輯,一般這種時(shí)候,你要是執(zhí)行一波,人家肯定毛都不會(huì)返回給你,為什么?因?yàn)槿思抑骶€程直接就退出了,還是那句話,你又沒告訴人家主線程要等這ip全部都ping 完,所以你必須要加個(gè)等待,等著Goroutine完成,這里我再舉一個(gè)網(wǎng)上的例子
package main import ( "fmt" ) func main() { go func() { fmt.Println("Goroutine 1") }() go func() { fmt.Println("Goroutine 2") }() //來個(gè)睡眠,等Goroutine結(jié)束 time.Sleep(time.Second * 1) }
看到了么,加了一個(gè)sleep,用sleep去等著Goroutine跑完,上面我舉的那個(gè)例子也可以這么來
listip := []string{"10.0.9.11","10.0.9.22","10.0.9.33"} for _, ip := range(listip) { //假設(shè)我們執(zhí)行一個(gè)ping ip 的邏輯 go PingIPWork(ip) } time.Sleep(time.Second * 1)
加個(gè)sleep可以等待完成,但是萬一啊,Goroutine有的跑的快,有的慢,你那sleep就一秒,要是有的Goroutine沒跑完不就白瞎了嗎,所以咱們需要一個(gè)機(jī)制,這個(gè)機(jī)制可以幫助咱們?nèi)ス芾鞧oroutine,讓我們知道Goroutine這東西什么時(shí)候停,什么時(shí)候完成。
所以,WaitGroup這個(gè)東西,就可以幫助我們解決這個(gè)問題,還是老樣子,我舉一個(gè)簡(jiǎn)單的例子來說明我的想法。
package main import ( "fmt" "sync" ) func PingIPWork(ip string) { fmt.Println(ip) } func main() { //定義一個(gè)等待阿祖 var wg sync.WaitGroup wg.Add(3) // 因?yàn)橛?個(gè)Ip,咱們定義三個(gè)動(dòng)作,所以來三個(gè)計(jì)數(shù) listip := []string{"10.0.9.11","10.0.9.22","10.0.9.33"} for _, ip := range(listip) { //假設(shè)我們執(zhí)行一個(gè)ping ip 的邏輯 go func(ip string) { //執(zhí)行一個(gè)work PingIPWork(ip) //操作完成之后,done一個(gè)計(jì)數(shù),也就是3-1 wg.Done() }(ip) } //等待 wg.Wait() // 等待,直到計(jì)數(shù)為0 }
這里我舉了一個(gè)簡(jiǎn)單的例子,其實(shí)wg的用法較為簡(jiǎn)單,在這個(gè)例子里面我們用到了
wg.wait 等待Goroutine結(jié)束之后退出主進(jìn)程 wg.Add 添加Goroutine,其實(shí)你可以把它想成,可添加的最大Goroutine數(shù) wg.Done 想象成銷毀參數(shù),當(dāng)Goroutine結(jié)束之后調(diào)用,意思就是,你沒了,我減1
WaitGroup的其他注意事項(xiàng)
將Wg作為參數(shù)進(jìn)行傳遞的時(shí)候,需要使用指針
有些時(shí)候,咱們不想寫的這么麻煩,就尋思怎么才能簡(jiǎn)單一點(diǎn),或者可變性稍微強(qiáng)一點(diǎn),有些時(shí)候我們要把wg最為參數(shù),在函數(shù)內(nèi)部調(diào)用,我們?cè)撛趺磳懩兀?/p>
package main import ( "fmt" "sync" ) func PingIPWork(ip string, wg *sync.WaitGroup) { fmt.Println(ip) wg.Done() } func main() { var wg sync.WaitGroup wg.Add(3) // 因?yàn)橛袃蓚€(gè)動(dòng)作,所以增加2個(gè)計(jì)數(shù) listip := []string{"10.0.9.11","10.0.9.22","10.0.9.33"} for _, ip := range(listip) { //假設(shè)我們執(zhí)行一個(gè)ping ip 的邏輯 go PingIPWork(ip, &wg) } wg.Wait() // 等待,直到計(jì)數(shù)為0 }
看到了么,如果你把Wg作為參數(shù)進(jìn)行傳遞,你得要用指針的形式傳值,否則就會(huì)死鎖?。。。。。。?!
Wg.Add的數(shù)值不能為負(fù)
wg.Add()的數(shù)值必須為正數(shù),如果為負(fù)數(shù),將會(huì)拋出異常。
panic: sync: negative WaitGroup counter goroutine 1 [running]: sync.(*WaitGroup).Add(0xc042008230, 0xffffffffffffff9c) D:/Go/src/sync/waitgroup.go:75 +0x1d0 main.main() D:/code/go/src/test-src/2-Package/sync/waitgroup/main.go:10 +0x54
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Go語言怎么使用變長(zhǎng)參數(shù)函數(shù)
本文主要介紹了Go語言怎么使用變長(zhǎng)參數(shù)函數(shù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07詳解golang中?work與?module?的區(qū)別與聯(lián)系
Go?模塊通常由一個(gè)項(xiàng)目或庫組成,并包含一組隨后一起發(fā)布的?Go?包,Go?模塊通過允許用戶將項(xiàng)目代碼放在他們選擇的目錄中并為每個(gè)模塊指定依賴項(xiàng)的版本,解決了原始系統(tǒng)的許多問題,本文將給大家介紹一下golang中?work與?module?的區(qū)別與聯(lián)系,需要的朋友可以參考下2023-09-09Golang中優(yōu)秀的消息隊(duì)列NSQ基礎(chǔ)安裝及使用詳解
這篇文章主要介紹了Golang中優(yōu)秀的消息隊(duì)列NSQ基礎(chǔ)安裝及使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12Golang實(shí)現(xiàn)支付寶沙箱支付的方法步驟
本文主要介紹了Golang實(shí)現(xiàn)支付寶沙箱支付的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04