[インデックス 15202] ファイルの概要
このコミットは、Go言語の標準ライブラリ sync
パッケージ内の WaitGroup
型の Add
メソッドに関するドキュメンテーションを改善するものです。具体的には、Add
メソッドを呼び出すタイミングに関する重要な注意点を追加し、WaitGroup
の誤用による潜在的なバグを防ぐことを目的としています。
コミット
commit 02e05817ada2bd3d0492387a032b1aa879c2cd3f
Author: Russ Cox <rsc@golang.org>
Date: Mon Feb 11 08:05:46 2013 -0500
sync: add caution about where to call (*WaitGroup).Add
Fixes #4762.
R=daniel.morsing, adg
CC=golang-dev
https://golang.org/cl/7308045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/02e05817ada2bd3d0492387a032b1aa879c2cd3f
元コミット内容
sync: add caution about where to call (*WaitGroup).Add
Fixes #4762.
R=daniel.morsing, adg
CC=golang-dev
https://golang.org/cl/7308045
変更の背景
この変更は、Go言語の sync.WaitGroup
を使用する際によく発生するバグ、特に「Add
が Wait
の後に呼ばれる」という競合状態の問題に対処するために行われました。WaitGroup
は、複数のゴルーチンが完了するまで待機するために使用される同期プリミティブです。しかし、wg.Add(delta)
の呼び出しが、対応するゴルーチンが起動する前、または wg.Wait()
が呼び出される前に適切に行われない場合、WaitGroup
が意図した数のゴルーチンを待機せずにゼロになり、プログラムが早期に終了したり、予期せぬ動作を引き起こしたりする可能性があります。
具体的には、Issue #4762 で報告された問題に対応しています。この問題は、wg.Add(1)
がゴルーチン内部で呼び出される場合に発生しやすく、メインゴルーチンが wg.Wait()
に到達する前に、すべてのゴルーチンが wg.Add(1)
を呼び出す機会を得られない可能性があるというものです。これにより、WaitGroup
のカウンターがまだゼロの状態で Wait()
が実行され、待機せずに通過してしまうというバグが発生します。
このコミットは、コードの動作を変更するのではなく、WaitGroup.Add
メソッドのドキュメンテーションに明確な警告を追加することで、開発者がこの一般的な落とし穴を回避できるようにすることを目的としています。
前提知識の解説
Go言語の並行処理とゴルーチン (Goroutines)
Go言語は、軽量な並行処理の仕組みとして「ゴルーチン (goroutine)」を提供します。ゴルーチンは、OSのスレッドよりもはるかに軽量で、数百万個を同時に実行することも可能です。go
キーワードを使って関数呼び出しの前に置くことで、その関数を新しいゴルーチンとして実行できます。
sync
パッケージと同期プリミティブ
Go言語の標準ライブラリ sync
パッケージは、並行処理における同期のためのプリミティブを提供します。これには、ミューテックス (sync.Mutex
)、条件変数 (sync.Cond
)、そしてこのコミットで扱われる WaitGroup
などがあります。
sync.WaitGroup
sync.WaitGroup
は、複数のゴルーチンが完了するまでメインゴルーチンが待機するためのメカニズムです。これは、カウンターを内部に持ち、以下の3つのメソッドで操作されます。
Add(delta int)
:WaitGroup
のカウンターにdelta
を加算します。delta
は正の値でも負の値でも構いません。通常、新しいゴルーチンを起動する前にAdd(1)
を呼び出してカウンターを増やし、待機するゴルーチンの数を指定します。Done()
:WaitGroup
のカウンターを1減らします。これはAdd(-1)
と同等です。通常、ゴルーチンの処理が完了したときにdefer wg.Done()
として呼び出されます。Wait()
:WaitGroup
のカウンターがゼロになるまでブロックします。カウンターがゼロになると、Wait()
はブロックを解除し、実行を続行します。
競合状態 (Race Condition)
競合状態とは、複数の並行プロセスやスレッドが共有リソースにアクセスする際に、そのアクセス順序によって結果が非決定的に変わってしまう状況を指します。WaitGroup
の「Add
が Wait
の後に呼ばれる」問題は、まさにこの競合状態の一例です。Wait()
がカウンターがゼロの状態で実行されてしまうと、意図した待機が行われないため、プログラムのロジックが破綻します。
技術的詳細
このコミットは、src/pkg/sync/waitgroup.go
ファイルの WaitGroup.Add
メソッドのコメントに新しい説明を追加しています。追加されたコメントは、Add
メソッドの呼び出しタイミングに関する重要なガイドラインを提供します。
変更前のコメントは以下の通りでした。
// Add adds delta, which may be negative, to the WaitGroup counter.
// If the counter becomes zero, all goroutines blocked on Wait() are released.
// If the counter goes negative, Add panics.
このコミットによって、以下の行が追加されました。
// 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.
この追加されたコメントのポイントは以下の通りです。
Add
の呼び出しタイミング: 「正のdelta
を持つ呼び出しは、Wait
の呼び出しよりも前に発生しなければならない」と明記しています。これは、WaitGroup
のカウンターが正しく設定されることを保証するための最も重要なルールです。- 潜在的な問題: もしこのルールが守られない場合、「
Wait
が小さすぎるグループを待つ可能性がある」と警告しています。これは、カウンターが意図した数よりも少ない状態でWait
が実行され、結果として一部のゴルーチンが完了する前にWait
が解除されてしまうことを意味します。 - 推奨されるプラクティス: 「通常、
Add
の呼び出しは、ゴルーチンを作成するステートメント、または待機する他のイベントの前に実行されるべきである」と具体的なガイダンスを提供しています。これは、go func() { ... }
の前にwg.Add(1)
を置くという、GoにおけるWaitGroup
の標準的なイディオムを強調しています。 - 例への参照: 「WaitGroup の例を参照してください」と、より具体的な使用例がドキュメントの他の場所にあることを示唆しています。
この変更は、Goの標準ライブラリのドキュメンテーションの品質向上に貢献し、開発者がより堅牢な並行プログラムを作成するのを助けます。コードの動作自体は変更されませんが、開発者の理解を深め、一般的なバグを未然に防ぐ効果があります。
コアとなるコードの変更箇所
変更は src/pkg/sync/waitgroup.go
ファイルの WaitGroup
型の Add
メソッドのコメント部分です。
--- a/src/pkg/sync/waitgroup.go
+++ b/src/pkg/sync/waitgroup.go
@@ -34,8 +34,13 @@ type WaitGroup struct {
// G3: Wait() // G1 still hasn\'t run, G3 finds sema == 1, unblocked! Bug.\n \n // Add adds delta, which may be negative, to the WaitGroup counter.\n-// If the counter becomes zero, all goroutines blocked on Wait() are released.\n+// If the counter becomes zero, all goroutines blocked on Wait are released.\n // If the counter goes negative, Add panics.\n+//\n+// Note that calls with positive delta must happen before the call to Wait,\n+// or else Wait may wait for too small a group. Typically this means the calls\n+// to Add should execute before the statement creating the goroutine or\n+// other event to be waited for. See the WaitGroup example.\n func (wg *WaitGroup) Add(delta int) {\n \tif raceenabled {\n \t\traceReleaseMerge(unsafe.Pointer(wg))\n```
## コアとなるコードの解説
このコミットは、`sync.WaitGroup` の `Add` メソッドのドキュメンテーションに、`Add` の呼び出しタイミングに関する重要な注意点を追加しています。
追加されたコメントは以下の通りです。
```go
// 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.
このコメントは、WaitGroup
を正しく使用するための最も重要な原則の一つを強調しています。それは、wg.Add(n)
(nは正の数) の呼び出しは、wg.Wait()
の呼び出しよりも常に前に行われるべきである、という点です。
なぜこれが重要なのでしょうか?
もし wg.Add(1)
がゴルーチン内部で呼び出され、かつそのゴルーチンの起動が遅れた場合、メインゴルーチンが wg.Wait()
に到達した時点で、WaitGroup
のカウンターがまだゼロである可能性があります。この場合、Wait()
はブロックせずにすぐに戻ってしまい、まだ開始されていない、または Add(1)
を呼び出していないゴルーチンを待つことができません。結果として、プログラムは意図した処理が完了する前に終了してしまう可能性があります。
このコメントは、この一般的な落とし穴を明示的に警告し、開発者に対して、ゴルーチンを起動する前に Add
を呼び出すという推奨されるプラクティスに従うよう促しています。これにより、WaitGroup
のカウンターが、待機すべきゴルーチンの数を正確に反映していることが保証され、競合状態によるバグを防ぐことができます。
関連リンク
- Go言語
sync
パッケージのドキュメンテーション: https://pkg.go.dev/sync - Go言語
sync.WaitGroup
のドキュメンテーション: https://pkg.go.dev/sync#WaitGroup - Go言語の並行処理に関する公式ブログ記事やドキュメント (一般的な情報源)
参考にした情報源リンク
- GitHub Issue #4762: https://github.com/golang/go/issues/4762
- Web検索結果 (Go
sync.WaitGroup
の「Add before Wait」バグに関する解説記事)- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEsAt7ZkKpmxLFdRta2SlQlVLSeBV3xEokyc_iDjNmnd-HVZR7duaiFj7Irv6L_OL_8GIAhWXFvTMO7Ft9j_u-eEMzEsiv-3ZZgHC-N_f71rUZqcWrpP866I4ejZxq3Ug_chDRUaBlKsLn0D1bI
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE17ep0JRs_WJaQbVqtx0-g_bOq7WpB7zdDhHTq10x4bhOJkvs-HgMDLR618aQst_ZKH7b-__YEqZVjWvr0Xlw1XYAh6c3b3O1sfLp-x80_ysREVQ1BtA5ZJq7UBucdQP8L1ne4BM17h3ELE9hbL1S5TdEpYqq9M59mYWhwqQYUy4HU9ntWlVd_0X3VXFjZTU_gUaxirVM=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEfYZ64n79h9SzQv3wD9HLH84zBefLyT-swfLQk-6TmPeyEjA-L6uET8yUFbrb4ny1EZkZG2olSZqP2e1_XQczRNwgtAv9-h8btUFT_4B3p9wID4xdM6uK_ednZSQPfGyDyt329GUQDYWs=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEKxnZGzCtSXuSlywu1OGjc_vcsoNl8K3XPRZCApvt_mECLNQEA-ZjpjSJ8pG-36oYqdQSXh2WC-7bASVho71mcJ8hUrOLLsiVF_hIapm1UyGSSFmncDdU_bx4NS7_-dJYXDVm8HxywkFg9enAGzUCEQ5ZjSpP7q8PpSeKY6zlF
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE5KM9_8y4n2W_EhulWT9QF1XBU2w5H85sgkjLBXh6xjUm8KmNa05TbFoyneVvgES2apdETazBa-I9MMd7idTp7aBvckFtf0eRMAAxU1hVt9svKt0hR1e7FVrD0Ifa-6hu8uNYPEuONcDyf4F2rvoRg8d_rW8OR51Sd9VUYPzKHRKwI9rK8YuQPkJfidA-NDekSULwpRtNoN_xeI9K5mfYgIQaIig==