




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
第Go語言學(xué)習(xí)之WaitGroup用法詳解目錄前言小試牛刀總覽底層實現(xiàn)結(jié)構(gòu)體AddDoneWait易錯點總結(jié)
前言
在前面的文章中,我們使用過WaitGroup進(jìn)行任務(wù)編排,Go語言中的WaitGroup和Java中的CyclicBarrier、CountDownLatch非常類似。比如我們有一個主任務(wù)在執(zhí)行,執(zhí)行到某一點時需要并行執(zhí)行三個子任務(wù),并且需要等到三個子任務(wù)都執(zhí)行完后,再繼續(xù)執(zhí)行主任務(wù)。那我們就需要設(shè)置一個檢查點,使主任務(wù)一直阻塞在這,等三個子任務(wù)執(zhí)行完后再放行。
說明:本文中的示例,均是基于Go1.1764位機(jī)器
小試牛刀
我們先來個簡單的例子,看下WaitGroup是怎么使用的。示例中使用Add(5)表示我們有5個子任務(wù),然后起了5個協(xié)程去完成任務(wù),主協(xié)程使用Wait()方法等待子協(xié)程執(zhí)行完畢,輸出一共等待的時間。
funcmain(){
varwaitGroupsync.WaitGroup
start:=time.Now()
waitGroup.Add(5)
fori:=0;ii++{
gofunc(){
deferwaitGroup.Done()
time.Sleep(time.Second)
fmt.Println("done")
waitGroup.Wait()
fmt.Println(time.Now().Sub(start).Seconds())
1.000306089
*/
總覽
WaitGroup一共有三個方法:
(wg*WaitGroup)Add(deltaint)
(wg*WaitGroup)Done()
(wg*WaitGroup)Wait()
Add方法用于設(shè)置WaitGroup的計數(shù)值,可以理解為子任務(wù)的數(shù)量Done方法用于將WaitGroup的計數(shù)值減一,可以理解為完成一個子任務(wù)Wait方法用于阻塞調(diào)用者,直到WaitGroup的計數(shù)值為0,即所有子任務(wù)都完成
正常來說,我們使用的時候,需要先確定子任務(wù)的數(shù)量,然后調(diào)用Add()方法傳入相應(yīng)的數(shù)量,在每個子任務(wù)的協(xié)程中,調(diào)用Done(),需要等待的協(xié)程調(diào)用Wait()方法,狀態(tài)流轉(zhuǎn)如下圖:
底層實現(xiàn)
結(jié)構(gòu)體
typeWaitGroupstruct{
noCopynoCopy//noCopy字段標(biāo)識,由于WaitGroup不能復(fù)制,方便工具檢測
state1[3]uint32//12個字節(jié),8個字節(jié)標(biāo)識計數(shù)值和等待數(shù)量,4個字節(jié)用于標(biāo)識信號量
}
state1是個復(fù)合字段,會拆分為兩部分:64位(8個字節(jié))的statep作為一個整體用于原子操作,其中前面4個字節(jié)表示計數(shù)值,后面四個字節(jié)表示等待數(shù)量;剩余32位(4個字節(jié))semap用于標(biāo)識信號量。
Go語言中對于64位的變量進(jìn)行原子操作,需要保證該變量是64位對齊的,也就是要保證這8個字節(jié)的首地址是8的整數(shù)倍。因此當(dāng)state1的首地址是8的整數(shù)倍時,取前8個字節(jié)作為statep,后4個字節(jié)作為semap;當(dāng)state1的首地址不是8的整數(shù)倍時,取后8個字節(jié)作為statep,前4個字節(jié)作為semap。
func(wg*WaitGroup)state()(statep*uint64,semap*uint32){
//首地址是8的倍數(shù)時,前8個字節(jié)為statep,后四個字節(jié)為semap
ifuintptr(unsafe.Pointer(wg.state1))%8==0{
return(*uint64)(unsafe.Pointer(wg.state1)),wg.state1[2]
}else{
//后8個字節(jié)為statep,前四個字節(jié)為semap
return(*uint64)(unsafe.Pointer(wg.state1[1])),wg.state1[0]
}
Add
Add方法用于添加一個計數(shù)值(負(fù)數(shù)相當(dāng)于減),當(dāng)計數(shù)值變?yōu)?后,Wait方法阻塞的所有等待者都會被釋放計數(shù)值變?yōu)樨?fù)數(shù)是非法操作,產(chǎn)生panic當(dāng)計數(shù)值為0時(初始狀態(tài)),Add方法不能和Wait方法并發(fā)調(diào)用,需要保證Add方法在Wait方法之前調(diào)用,否則會panic
func(wg*WaitGroup)Add(deltaint){
//拿到計數(shù)值等待者變量statep和信號量semap
statep,semap:=wg.state()
//計數(shù)值加上delta:statep的前四個字節(jié)是計數(shù)值,因此將delta前移32位
state:=atomic.AddUint64(statep,uint64(delta)32)
//計數(shù)值
v:=int32(state32)
//等待者數(shù)量
w:=uint32(state)
//如果加上delta之后,計數(shù)值變?yōu)樨?fù)數(shù),不合法,panic
ifv0{
panic("sync:negativeWaitGroupcounter")
//delta0v==int32(delta):表示從0開始添加計數(shù)值
//w!=0:表示已經(jīng)有了等待者
//說明在添加計數(shù)值的時候,同時添加了等待者,非法操作。添加等待者需要在添加計數(shù)值之后
ifw!=0delta0v==int32(delta){
panic("sync:WaitGroupmisuse:AddcalledconcurrentlywithWait")
//v0:計數(shù)值不等于0,不需要喚醒等待者,直接返回
//w==0:沒有等待者,不需要喚醒,直接返回
ifv0||w==0{
return
//再次檢查數(shù)據(jù)是否一致
if*statep!=state{
panic("sync:WaitGroupmisuse:AddcalledconcurrentlywithWait")
//到這里說明計數(shù)值為0,且等待者大于0,需要喚醒所有的等待者,并把系統(tǒng)置為初始狀態(tài)(0狀態(tài))
//將計數(shù)值和等待者數(shù)量都置為0
*statep=0
//喚醒等待者
for;w!=0;w--{
runtime_Semrelease(semap,false,0)
}
Done
//完成一個任務(wù),將計數(shù)值減一,當(dāng)計數(shù)值減為0時,需要喚醒所有的等待者
func(wg*WaitGroup)Done(){
wg.Add(-1)
}
Wait
//調(diào)用Wait方法會被阻塞,直到計數(shù)值變?yōu)?
func(wg*WaitGroup)Wait(){
//獲取計數(shù)、等待數(shù)和信號量
statep,semap:=wg.state()
for{
state:=atomic.LoadUint64(statep)
//計數(shù)值
v:=int32(state32)
//等待者數(shù)量
w:=uint32(state)
//計數(shù)值數(shù)量為0,直接返回,無需等待
ifv==0{
return
//到這里說明計數(shù)值數(shù)量大于0
//增加等待者數(shù)量:這里會有競爭,比如多個Wait調(diào)用,或者在同時調(diào)用Add方法,增加不成功會繼續(xù)for循環(huán)
ifatomic.CompareAndSwapUint64(statep,state,state+1){
//增加成功后,阻塞在信號量這里,等待被喚醒
runtime_Semacquire(semap)
//被喚醒的時候,應(yīng)該是0狀態(tài)。如果重用WaitGroup,需要等Wait返回
if*statep!=0{
panic("sync:WaitGroupisreusedbeforepreviousWaithasreturned")
return
}
易錯點
上面分析源碼可以看到幾個會產(chǎn)生panic的點,這也是我們使用WaitGroup需要注意的地方
1.計數(shù)值變?yōu)樨?fù)數(shù)
調(diào)用Add時參數(shù)值傳負(fù)數(shù)
funcmain(){
varwgsync.WaitGroup
wg.Add(1)
wg.Add(-1)
wg.Add(-1)
}
多次調(diào)用Done方法
funcmain(){
varwgsync.WaitGroup
wg.Add(1)
gofunc(){
fmt.Println("test")
wg.Done()
wg.Done()
time.Sleep(time.Second)
wg.Wait()
}
2.Add和Wait并發(fā)調(diào)用
Add和Wait并發(fā)調(diào)用,有可能達(dá)不到我們預(yù)期的效果,甚至panic。如下示例中,我們想要等待3個子任務(wù)都執(zhí)行完后再執(zhí)行主任務(wù),但實際情況可能是子任務(wù)還沒起來,主任務(wù)就繼續(xù)往下執(zhí)行了。
funcdoSomething(wg*sync.WaitGroup){
wg.Add(1)
fmt.Println("dosomething")
deferwg.Done()
funcmain(){
varwgsync.WaitGroup
fori:=0;ii++{
godoSomething(wg)
wg.Wait()
fmt.Println("main")
//main
//dosomething
//dosomething
正確的使用方式,應(yīng)該是在調(diào)用Wait前先調(diào)用Add
funcdoSomething(wg*sync.WaitGroup){
deferwg.Done()
fmt.Println("dosomething")
funcmain(){
varwgsync.WaitGroup
wg.Add(3)
fori:=0;ii++{
godoSomething(wg)
wg.Wait()
fmt.Println("main")
//dosomething
//dosomething
//dosomething
//main
3.沒有等Wait返回,就重用WaitGroup
funcmain(){
varwgsync.WaitGroup
wg.Add(1)
gofunc(){
fmt.Println("dosomething")
wg.Done()
wg.Add(1)
wg.Wait()
}
4.復(fù)制使用
我們知道Go語言中的參數(shù)傳遞,都是值傳遞,就會產(chǎn)生復(fù)制操作。因此在向函數(shù)傳遞WaitGroup時,使用指針進(jìn)行操作。
//錯誤使用方式,沒有使用指針
funcdoSomething(wgsync.WaitGroup){
fmt.Println("dosomething")
deferwg.Done()
funcmain(){
varwgsync.WaitGroup
wg.Add(3)
fori:=0;ii++{
//這里沒使用指針,wg狀態(tài)一直不會改變,導(dǎo)致W
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 【正版授權(quán)】 IEC 62425:2025 CMV EN Railway applications - Communication,signalling and processing systems - Safety related electronic systems for signalling
- 國際貿(mào)易實務(wù)模擬試題(附答案)
- 養(yǎng)殖業(yè)廢棄物處理服務(wù)合同
- 社區(qū)合作社種植產(chǎn)品收購與銷售協(xié)議
- 企業(yè)年度銷售代理協(xié)議
- 家用電器購買安裝合同
- 2025陜西西鳳露酒有限公司招聘(38人)筆試參考題庫附帶答案詳解
- 2025年山東土地資本投資集團(tuán)有限公司春季社會招聘(10人)筆試參考題庫附帶答案詳解
- 2025年中石化蕪湖石油分公司招聘8人筆試參考題庫附帶答案詳解
- 2025中儲糧集團(tuán)黑龍江分公司招聘165人查看職位筆試參考題庫附帶答案詳解
- 思想道德與法治2021版第六章第二節(jié)
- 工業(yè)機(jī)器人技術(shù)畢業(yè)論文范文
- DB11-T 2154-2023 城市軌道交通工程淺埋暗挖法施工技術(shù)規(guī)程
- 錫爐溫度及助焊劑比重測試記錄
- 地球物理勘探-第三章磁法勘探1
- Django 3 Web應(yīng)用開發(fā)實戰(zhàn)(上篇)
- 施工單位主體驗收自評報告
- 腎臟內(nèi)科臨床診療指南及操作規(guī)范
- DB32/T 4454-2023智慧化工園區(qū)建設(shè)規(guī)范
- 10kV保護(hù)定值計算明細(xì)表
- 圖形創(chuàng)意(高職藝術(shù)設(shè)計類)PPT完整全套教學(xué)課件
評論
0/150
提交評論