日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

GoLang的sync.WaitGroup與sync.Once簡單使用講解_Golang

作者:鯤鵬飛九萬里 ? 更新時間: 2023-02-23 編程語言

一、sync.WaitGroup的簡單實用

在之前,我們使用通道,來主goroutine中等待其他goroutine執行完成:

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類型。它比通道更加適合實現這種一對多的 goroutine 協作流程。

sync.WaitGroup類型(以下簡稱WaitGroup類型)是開箱即用的,也是并發安全的。同時,它一旦被真正使用就不能被復制了。

WaitGroup類型擁有三個指針方法:Add、Done和Wait。

Add方法

可以想象該類型中有一個計數器,它的默認值是0。我們可以通過調用該類型值的Add方法來增加,或者減少這個計數器的值。

Done方法

用這個方法來記錄需要等待的 goroutine 的數量。相對應的,這個類型的Done方法,用于對其所屬值中計數器的值進行減一操作。我們可以在需要等待的 goroutine 中,通過defer語句調用它。

Wait方法

此類型的Wait方法的功能是,阻塞當前的 goroutine,直到其所屬值中的計數器歸零。如果在該方法被調用的時候,那個計數器的值就是0,那么它將不會做任何事情。

二、sync.WaitGroup類型值中計數器的值可以小于0嗎

不可以。

之所以說WaitGroup值中計數器的值不能小于0,是因為這樣會引發一個 panic。 不適當地調用這類值的Done方法和Add方法都會如此。

  • 雖然WaitGroup值本身并不需要初始化,但是盡早地增加其計數器的值,還是非常有必要的。
  • WaitGroup值是可以被復用的,但需要保證其計數周期的完整性。
  • 不要把增加其計數器值的操作和調用其Wait方法的代碼,放在不同的 goroutine 中執行。換句話說,要杜絕對同一個WaitGroup值的兩種操作的并發執行。

三、sync.Once

sync.Once也屬于結構體類型,同樣也是開箱即用和并發安全的。由于這個類型包含了一個sync.Mutex類型的字段,所以,復制該類型的值也會導致功能的失效。

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方法只接受一個參數,這個參數的類型必須是func(),即無參數聲明和結果聲明的函數。

該方法的功能并不是對每一種參數函數都只執行一次,而是只執行“首次被調用時傳入的”那個函數,并且之后不會再執行任何參數函數。

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

$

所以,如果你有多個只需要執行一次的函數,那么就應該為它們中每一個都分配一個sync.Once類型的值。

sync.Once類型中的uint32類型的字段

sync.Once類型中有一個名叫done的uint32類型的字段。它的作用是記錄其所屬值的Do方法被調用的次數。該字段的值只可能為0或1。

一旦Do方法首次調用完成,它的值就會從0變為1。

使用uint32 類型是為了保證原子性。

修改done,使用了“雙重判斷+鎖”的方式,類似于GoF設計模式中的單例模式。

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方法只會在參數函數執行結束之后把done字段的值變為1,因此,如果參數函數的執行需要很長時間或者根本就不會結束(比如執行一些守護任務),那么就有可能會導致相關 goroutine 的同時阻塞。

第二個特點:Do方法在參數函數執行結束后,對done字段的賦值用的是原子操作,并且,這一操作是被掛在defer語句中的。因此,不論參數函數的執行會以怎樣的方式結束,done字段的值都會變為1。

也就是說,即使這個參數函數沒有執行成功(比如引發了一個 panic),我們也無法使用同一個Once值重新執行它了。所以,如果你需要為參數函數的執行設定重試機制,那么就要考慮Once值的適時替換問題。

原文鏈接:https://blog.csdn.net/hefrankeleyn/article/details/128606310

欄目分類
最近更新