8

単一のゴルーチンが不確定な数の子ゴルーチンを起動し、さらに多くのゴルーチンを起動するコードがあります。私の目標は、すべての子ゴルーチンが終了するのを待つことです。

事前に起動するゴルーチンの総数がわからないため、sync.WaitGroupを使用できません。理想的には、 channel-as-semaphoreパターンを介して実行されるゴルーチンの総数を人為的に制限する必要はありません。.

手短に言えば、各ゴルーチンに、そのすべての子を待機するセマフォとして機能するローカル チャネルまたは待機グループを用意することを考えましたが、その結果、各ゴルーチンは、すべての子孫が終了する間、スタック スペースを消費することになります。

今のところ私の考えは、ゴルーチンが起動されたときにアトミックカウンターをインクリメントし(親で、親が終了した後に子が実行を開始した場合に誤ってゼロにヒットするのを避けるため)、ゴルーチンが終了したときにそれをデクリメントし、等しいかどうかを定期的にチェックすることですゼロに。

私は基本的に正しい方向に進んでいますか、それとももっとエレガントな解決策がありますか?

4

3 に答える 3

11

の最初の実装を書きましたがsync.WaitGroup、これと他のエッジ ケースは十分にサポートされていました。それ以来、実装は Dmitry によって改善されました。彼の実績を考えると、彼はそれをより安全にしただけだと思います。

特に、現在 1 つ以上のブロックされたWait呼び出しがあり、 を呼び出すAdd前に正のデルタで呼び出したDone場合、以前に存在したWait呼び出しのブロックを解除しないことを信頼できます。

したがって、たとえば次のように間違いなく実行できます。

var wg sync.WaitGroup
wg.Add(1)
go func() {
    wg.Add(1)
    go func() {
        wg.Done()
    }()
    wg.Done()
}()
wg.Wait()

コードが最初に統合されて以来、私は実際に本番環境で同等のロジックを使用しています。

参考までに、この内部コメントは最初の実装で配置され、現在も残っています。

// WaitGroup creates a new semaphore each time the old semaphore
// is released. This is to avoid the following race:
//
// G1: Add(1)
// G1: go G2()
// G1: Wait() // Context switch after Unlock() and before Semacquire().
// G2: Done() // Release semaphore: sema == 1, waiters == 0. G1 doesn't run yet.
// G3: Wait() // Finds counter == 0, waiters == 0, doesn't block.
// G3: Add(1) // Makes counter == 1, waiters == 0.
// G3: go G4()
// G3: Wait() // G1 still hasn't run, G3 finds sema == 1, unblocked! Bug.

これは、実装に触れる際に留意すべき別の競合状態を説明していますG1Add(1) + go f()G3.

ただし、最近そこに置かれたドキュメントに紛らわしい声明があるため、あなたの質問は理解していますが、コメントの履歴を見て、実際に何を扱っているかを確認してください。

コメントは、リビジョン 15683 で Russ によってそこに置かれました。

(...)
+// Note that calls with positive delta must happen before the call to Wait,
+// or else Wait may wait for too small a group. Typically this means the calls
+// to Add should execute before the statement creating the goroutine or
+// other event to be waited for. See the WaitGroup example.
func (wg *WaitGroup) Add(delta int) {

Russ のログ コメントには次のように記載されています。

sync: (*WaitGroup).Add を呼び出す場所に関する注意を追加します。

問題 4762 を修正します。

問題 4762 を読むと、次のことがわかります。

Done の呼び出しを含む go ルーチンを起動する前に Add の呼び出しを行う必要があることを、sync.WaitGroup のドキュメントに明示的なコメントを追加する価値がある場合があります。

したがって、ドキュメントは実際には次のようなコードに対して警告しています。

var wg sync.WaitGroup
wg.Add(1)
go func() {
    go func() {
        wg.Add(1)
        wg.Done()
    }()
    wg.Done()
}()
wg.Wait()

これは確かに壊れています。コメントは、より具体的になるように改善し、もっともらしいが誤解を招くような理解を避ける必要があります。

于 2013-09-14T23:30:59.527 に答える