sync.WaitGroup
は、同時環境で非常に一般的に使用されるデータ構造であり、すべての処理を待機するために使用されます。コルーチンの末尾はコードを書く際のサンプルに従って記述されており、使い方を掘り下げる必要はありません。数日前、コルーチンで Add()
関数を実行できるかどうか疑問に思いましたが、答えは「ノー」です。
罠は、WaitGroup の 3 つの関数の呼び出しシーケンスにあります。まず、3 つの関数の機能を確認してみましょう。
-
Add(delta int)
: デルタをカウンターに追加します。たとえば、コルーチンの開始時に 1 ずつ増加します。 -
Done()
: コルーチンが終了する前に実行され、カウンターが 1 減算されます。 -
Wait()
: ブロッキング待機カウンターは 0 です。
テストのテスト
次のプログラムは、親コルーチンを作成し、親コルーチンは 10 個のサブコルーチンを作成します。main 関数は、すべてのコルーチンが終了するのを待ってから終了します。以下のコードに問題があるかどうかを確認してください。
package main import ( "fmt" "sync" ) func father(wg *sync.WaitGroup) { wg.Add(1) defer wg.Done() fmt.Printf("father\n") for i := 0; i < 10; i++ { go child(wg, i) } } func child(wg *sync.WaitGroup, id int) { wg.Add(1) defer wg.Done() fmt.Printf("child [%d]\n", id) } func main() { var wg sync.WaitGroup go father(&wg) wg.Wait() log.Printf("main: father and all chindren exit") }
問題は見つかりましたか?次の実行結果が表示されない場合: サブコルーチンが終了する前にメイン関数が終了し始めます。
father main: father and all chindren exit child [9] child [0] child [4] child [7] child [8]
トラップ分析
上記の問題の理由は、Add()
関数がコルーチンの作成後にコルーチン内で実行され、この時点で Wait ()
関数 は既に実行されているか、すべての Add() が実行される前に
Wait() 関数が実行される可能性があります。
Wait( )実行すると、即座にWaitGroupのカウンタが0になり、Waitが終了してメインプログラムが終了するため、サブコルーチンが全て終了せずにメイン関数が終了します。
Add 関数は、Wait 関数が実行される前に実行する必要があります。これは、Add 関数のドキュメントで指示されています。カウンタがゼロのときに発生する正のデルタは、Wait. の前に発生する必要があります。 Add 関数が Wait 関数の前に実行されるようにするにはどうすればよいですか?コルーチンの場合、コルーチン内のコードの実行時間が Wait 関数の実行時間より早いかどうかは予測できませんが、コルーチンを割り当てる前に Add 関数を実行し、その後 Wait 関数を実行することで確実に予測できます。関数。
以下は修正したプログラムと出力結果です。
リーリーリー