の最初の実装を書きましたが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.
これは、実装に触れる際に留意すべき別の競合状態を説明していますG1
がAdd(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()
これは確かに壊れています。コメントは、より具体的になるように改善し、もっともらしいが誤解を招くような理解を避ける必要があります。