GO の sync.WaitGroup と sync.Once

GO の sync.WaitGroup と sync.Once

1.sync.WaitGroupシンプルで実用的

以前は、チャネルを使用してメインのゴルーチンで他のゴルーチンが完了するのを待ちました。

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()
}

同期パッケージの WaitGroup タイプ。この 1 対多のゴルーチン調整プロセスの実装には、チャネルよりも適しています。

sync.WaitGroup型 (以降、WaitGroup 型と呼びます) はそのまま使用でき、同時実行セーフでもあります。同時に、一度実際に使用すると、コピーすることはできません。

WaitGroup 型には、Add、Done、および Wait の 3 つのポインター メソッドがあります。

(1) メソッドの追加

このタイプにはカウンターが存在することが考えられ、そのデフォルト値は 0 です。このタイプの値の Add メソッドを呼び出すことで、このカウンターの値をインクリメントまたはデクリメントできます。

(2) 完了方法

このメソッドを使用して、待機する必要があるゴルーチンの数を追跡します。同様に、このタイプの Done メソッドは、カウンターの値をそれ自身の値で 1 減らすために使用されます。待機する必要があるゴルーチンの defer ステートメントを介して呼び出すことができます。

(3) 待機方法

このタイプの Wait メソッドの機能は、独自の値のカウンターがゼロになるまで現在のゴルーチンをブロックすることです。このメソッドが呼び出されたときにそのカウンターの値が 0 の場合、何もしません。

2. sync.WaitGroup タイプ値のカウンターの値が 0 未満になることはありますか?

できません。

WaitGroup 値のカウンターの値が 0 未満にならないのは、パニックが発生するためです。これは、このような値を不適切に呼び出す Done メソッドと Add メソッドの両方に当てはまります。

  • WaitGroup 値自体を初期化する必要はありませんが、できるだけ早くカウンターの値を増やす必要があります。
  • WaitGroup 値は再利用できますが、そのカウント サイクルの整合性を保証する必要があります。
  • カウンタの値を増やす操作と Wait メソッドを呼び出すコードを別のゴルーチンに配置しないでください。つまり、同じ WaitGroup 値に対して 2 つの操作が同時に実行されないようにする必要があります。

三つ、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
}

(1) 使い方

Oncetype のメソッドDoは 1 つのパラメーターのみを受け入れます。このパラメーターの型は である必要がありますfunc()。つまり、パラメーター宣言と結果宣言のない関数です。

このメソッドの機能は、各パラメーター関数を 1 回だけ実行するのではなく、「最初に呼び出されたときに渡された」関数のみを実行し、その後はパラメーター関数を実行しません。

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の値を割り当てる必要があります。

(2) sync.Oncetype の uint32 型のフィールド

sync.Onceこの型には、doneuint32 という名前のフィールドがあります。その役割は、その値の Do メソッドが呼び出された回数を記録することです。このフィールドの値は、0 または 1 のみです。

Do メソッドが最初に呼び出されると、その値は 0 から 1 に変わります。

uint32 型は原子性を保証するために使用されます。

修正は、doneGoFデザインパターンのシングルトンパターンと同様の「二重判定+ロック」方式を採用しています。

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()
	}
}

(3) Do法の機能的特徴

1 つ目の特徴: Do メソッドは、パラメーター関数の実行が完了した後にのみ done フィールドの値を 1 に変更するため、パラメーター関数の実行に時間がかかるか、まったく終了しない場合 (たとえば、いくつかのガーディアン タスクを実行している場合)、関連するゴルーチンが同時にブロックされる可能性があります。

2 番目の機能: パラメーター関数の実行後、Do メソッドはアトミック操作を使用して done フィールドを割り当て、この操作は defer ステートメントでハングします。したがって、パラメータ関数の実行がどのように終了しても、done フィールドの値は 1 になります。

つまり、このパラメーター関数が正常に実行されなかった場合 (たとえば、パニックがトリガーされた場合) に、同じ Once 値で再実行することはできません。したがって、パラメーター関数の実行に再試行メカニズムを設定する必要がある場合は、Once 値をタイムリーに置き換えることを検討する必要があります。

おすすめ

転載: blog.csdn.net/hefrankeleyn/article/details/128606310