GoLang的sync.WaitGroup與sync.Once簡單使用講解
一、sync.WaitGroup的簡單實用
在之前,我們使用通道,來主goroutine中等待其他goroutine執(zhí)行完成:
func coordinateWithChan() { sign := make(chan struct{}, 2) num := int32(0) fmt.Printf("The number: %d [with chan struct{}]\n", num) max := int32(10) go addNum(&num, 1, max, func() { sign <- struct{}{} }) go addNum(&num, 2, max, func() { sign <- struct{}{} }) <-sign <-sign }
其實,可以用更簡單的方法,使用sync.WaitGroup
來做:
func coordinateWithWaitGroup() { var wg sync.WaitGroup wg.Add(2) num := int32(0) fmt.Printf("The number: %d [with sync.WaitGroup]\n", num) max := int32(10) go addNum(&num, 3, max, wg.Done) go addNum(&num, 4, max, wg.Done) wg.Wait() }
sync包的WaitGroup類型。它比通道更加適合實現(xiàn)這種一對多的 goroutine 協(xié)作流程。
sync.WaitGroup
類型(以下簡稱WaitGroup類型)是開箱即用的,也是并發(fā)安全的。同時,它一旦被真正使用就不能被復(fù)制了。
WaitGroup類型擁有三個指針方法:Add、Done和Wait。
Add方法
可以想象該類型中有一個計數(shù)器,它的默認值是0。我們可以通過調(diào)用該類型值的Add方法來增加,或者減少這個計數(shù)器的值。
Done方法
用這個方法來記錄需要等待的 goroutine 的數(shù)量。相對應(yīng)的,這個類型的Done方法,用于對其所屬值中計數(shù)器的值進行減一操作。我們可以在需要等待的 goroutine 中,通過defer語句調(diào)用它。
Wait方法
此類型的Wait方法的功能是,阻塞當(dāng)前的 goroutine,直到其所屬值中的計數(shù)器歸零。如果在該方法被調(diào)用的時候,那個計數(shù)器的值就是0,那么它將不會做任何事情。
二、sync.WaitGroup類型值中計數(shù)器的值可以小于0嗎
不可以。
之所以說WaitGroup值中計數(shù)器的值不能小于0,是因為這樣會引發(fā)一個 panic。 不適當(dāng)?shù)卣{(diào)用這類值的Done方法和Add方法都會如此。
- 雖然WaitGroup值本身并不需要初始化,但是盡早地增加其計數(shù)器的值,還是非常有必要的。
- WaitGroup值是可以被復(fù)用的,但需要保證其計數(shù)周期的完整性。
- 不要把增加其計數(shù)器值的操作和調(diào)用其Wait方法的代碼,放在不同的 goroutine 中執(zhí)行。換句話說,要杜絕對同一個WaitGroup值的兩種操作的并發(fā)執(zhí)行。
三、sync.Once
sync.Once
也屬于結(jié)構(gòu)體類型,同樣也是開箱即用和并發(fā)安全的。由于這個類型包含了一個sync.Mutex
類型的字段,所以,復(fù)制該類型的值也會導(dǎo)致功能的失效。
type Once struct { // done indicates whether the action has been performed. // It is first in the struct because it is used in the hot path. // The hot path is inlined at every call site. // Placing done first allows more compact instructions on some architectures (amd64/386), // and fewer instructions (to calculate offset) on other architectures. done uint32 m Mutex }
用法
Once
類型的Do
方法只接受一個參數(shù),這個參數(shù)的類型必須是func()
,即無參數(shù)聲明和結(jié)果聲明的函數(shù)。
該方法的功能并不是對每一種參數(shù)函數(shù)都只執(zhí)行一次,而是只執(zhí)行“首次被調(diào)用時傳入的”那個函數(shù),并且之后不會再執(zhí)行任何參數(shù)函數(shù)。
package main import ( "fmt" "sync" "sync/atomic" ) func main() { var counter uint32 var once sync.Once once.Do(func() { atomic.AddUint32(&counter, 1) }) fmt.Printf("The counter: %d\n", counter) once.Do(func() { atomic.AddUint32(&counter, 2) }) fmt.Printf("The counter: %v\n", counter) fmt.Println() }
$ go run demo02.go
The counter: 1
The counter: 1$
所以,如果你有多個只需要執(zhí)行一次的函數(shù),那么就應(yīng)該為它們中每一個都分配一個sync.Once
類型的值。
sync.Once類型中的uint32類型的字段
sync.Once
類型中有一個名叫done
的uint32類型的字段。它的作用是記錄其所屬值的Do方法被調(diào)用的次數(shù)。該字段的值只可能為0或1。
一旦Do方法首次調(diào)用完成,它的值就會從0變?yōu)?。
使用uint32 類型是為了保證原子性。
修改done
,使用了“雙重判斷+鎖”的方式,類似于GoF設(shè)計模式中的單例模式。
func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 0 { // Outlined slow-path to allow inlining of the fast-path. o.doSlow(f) } } func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } }
Do方法的功能特點
第一個特點:于Do方法只會在參數(shù)函數(shù)執(zhí)行結(jié)束之后把done字段的值變?yōu)?,因此,如果參數(shù)函數(shù)的執(zhí)行需要很長時間或者根本就不會結(jié)束(比如執(zhí)行一些守護任務(wù)),那么就有可能會導(dǎo)致相關(guān) goroutine 的同時阻塞。
第二個特點:Do方法在參數(shù)函數(shù)執(zhí)行結(jié)束后,對done字段的賦值用的是原子操作,并且,這一操作是被掛在defer語句中的。因此,不論參數(shù)函數(shù)的執(zhí)行會以怎樣的方式結(jié)束,done字段的值都會變?yōu)?。
也就是說,即使這個參數(shù)函數(shù)沒有執(zhí)行成功(比如引發(fā)了一個 panic),我們也無法使用同一個Once值重新執(zhí)行它了。所以,如果你需要為參數(shù)函數(shù)的執(zhí)行設(shè)定重試機制,那么就要考慮Once值的適時替換問題。
到此這篇關(guān)于GoLang的sync.WaitGroup與sync.Once簡單使用講解的文章就介紹到這了,更多相關(guān)Go sync.WaitGroup與sync.Once內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang實現(xiàn)openssl自簽名雙向認證的詳細步驟
這篇文章主要介紹了golang實現(xiàn)openssl自簽名雙向認證的詳細步驟,本文分步驟給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-03-03詳解Golang如何實現(xiàn)一個環(huán)形緩沖器
環(huán)形緩沖器(ringr?buffer)是一種用于表示一個固定尺寸、頭尾相連的緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu),適合緩存數(shù)據(jù)流。本文將利用Golang實現(xiàn)一個環(huán)形緩沖器,需要的可以參考一下2022-09-09Go語言基于HTTP的內(nèi)存緩存服務(wù)的實現(xiàn)
這篇文章主要介紹了Go語言基于HTTP的內(nèi)存緩存服務(wù),本程序采用REST接口,支持設(shè)置(Set)、獲取(Get)和刪除(Del)這3個基本操作,同時還支持對緩存服務(wù)狀態(tài)進行查詢,需要的朋友可以參考下2022-08-08一個Pod調(diào)度失敗后重新觸發(fā)調(diào)度的所有情況分析
這篇文章主要為大家介紹了一個Pod調(diào)度失敗后重新觸發(fā)調(diào)度的所有情況分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04