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) 使い方
Once
type のメソッド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.Once
type の 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()
}
}
(3) Do法の機能的特徴
1 つ目の特徴: Do メソッドは、パラメーター関数の実行が完了した後にのみ done フィールドの値を 1 に変更するため、パラメーター関数の実行に時間がかかるか、まったく終了しない場合 (たとえば、いくつかのガーディアン タスクを実行している場合)、関連するゴルーチンが同時にブロックされる可能性があります。
2 番目の機能: パラメーター関数の実行後、Do メソッドはアトミック操作を使用して done フィールドを割り当て、この操作は defer ステートメントでハングします。したがって、パラメータ関数の実行がどのように終了しても、done フィールドの値は 1 になります。
つまり、このパラメーター関数が正常に実行されなかった場合 (たとえば、パニックがトリガーされた場合) に、同じ Once 値で再実行することはできません。したがって、パラメーター関数の実行に再試行メカニズムを設定する必要がある場合は、Once 値をタイムリーに置き換えることを検討する必要があります。