Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13482] ファイルの概要

このコミットは、Go言語の標準ライブラリ sync パッケージ内の WaitGroup 型のドキュメントとエラーメッセージの改善に関するものです。具体的には、WaitGroup.Add メソッドに負の値を渡してカウンタが負になった場合に発生するパニックメッセージをより明確にし、その挙動をドキュメントに明記しています。

コミット

  • コミットハッシュ: 55ff3f7076e9ef45f8c853eece8acf36e891d885
  • 作者: Rob Pike r@golang.org
  • コミット日時: 2012年7月19日 木曜日 11:55:03 -0700

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/55ff3f7076e9ef45f8c853eece8acf36e891d885

元コミット内容

sync: mention that WaitGroup.Add panics
Fixes #3839.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6428053

変更の背景

このコミットは、Go言語のIssue #3839を修正するために行われました。sync.WaitGroup は、複数のゴルーチンが完了するのを待つための同期プリミティブです。Add メソッドはカウンタを増減させますが、このカウンタが負の値になることは論理的に不正な状態です。以前のバージョンでは、カウンタが負になった場合にパニックが発生していましたが、そのパニックメッセージが「sync: negative WaitGroup count」と表示されていました。

この変更の背景には、以下の点が考えられます。

  1. 明確なエラーメッセージ: 「count」という単語よりも「counter」という単語の方が、WaitGroup の内部状態を指す上でより正確で分かりやすいと判断された可能性があります。これにより、開発者がパニックの原因を特定しやすくなります。
  2. ドキュメントの改善: WaitGroup.Add のドキュメントに「If the counter goes negative, Add panics.」という記述を追加することで、このメソッドの挙動に関する重要な制約を明示し、誤用を防ぐことを目的としています。これにより、ユーザーは WaitGroup を使用する際に、カウンタが負にならないように注意する必要があることを事前に知ることができます。
  3. 堅牢性の向上: パニックメッセージの改善とドキュメントの追加は、ライブラリの堅牢性と使いやすさを向上させるための一般的なプラクティスです。

前提知識の解説

Go言語の sync.WaitGroup

sync.WaitGroup は、Go言語で複数のゴルーチンが完了するのを待つための同期メカニズムです。主に以下の3つのメソッドで構成されます。

  • Add(delta int): WaitGroup のカウンタに delta を加算します。通常、新しいゴルーチンを開始する前に Add(1) を呼び出してカウンタを増やします。delta は負の値も取ることができ、これによりカウンタを減らすことも可能です。
  • Done(): Add(-1) のショートカットです。通常、ゴルーチンがその処理を完了したときに呼び出され、カウンタを1減らします。
  • Wait(): WaitGroup のカウンタがゼロになるまでブロックします。すべてのゴルーチンが Done() を呼び出してカウンタがゼロになると、Wait() はブロックを解除します。

Go言語の panic

Go言語における panic は、プログラムの実行を停止させる回復不可能なエラーを示すメカニズムです。panic が発生すると、現在のゴルーチンの通常の実行フローは停止し、遅延関数(defer)が実行され、その後、コールスタックを遡ってパニックが伝播します。もし、スタックのどこにも recover 関数が呼び出されていない場合、プログラム全体がクラッシュします。

panic は、通常、プログラマの論理的な誤りや、プログラムが継続できないような予期せぬ状態(例: 配列の範囲外アクセス、nilポインタのデリファレンス)が発生した場合に使用されます。WaitGroup のカウンタが負になることは、WaitGroup の設計上の意図に反する不正な状態であるため、panic を発生させることでその誤用を開発者に知らせています。

atomic.AddInt32

atomic パッケージは、低レベルのアトミック操作を提供します。アトミック操作は、複数のゴルーチンが同時にアクセスしても、その操作が中断されることなく完全に実行されることを保証します。atomic.AddInt32 は、int32 型の変数にアトミックに値を加算する関数です。WaitGroup のカウンタは複数のゴルーチンから同時にアクセスされる可能性があるため、データ競合を防ぐためにアトミック操作が使用されています。

技術的詳細

このコミットの技術的な変更点は以下の2つです。

  1. WaitGroup.Add メソッドのドキュメント更新: src/pkg/sync/waitgroup.goWaitGroup.Add メソッドのコメントに、以下の行が追加されました。

    // If the counter goes negative, Add panics.
    

    これにより、Add メソッドに負の値を渡してカウンタが負になった場合にパニックが発生するという重要な挙動が明示的にドキュメント化されました。これは、ライブラリのAPI契約の一部となり、ユーザーが WaitGroup を正しく使用するためのガイドラインを提供します。

  2. パニックメッセージの変更: src/pkg/sync/waitgroup.go 内のパニックメッセージが、panic("sync: negative WaitGroup count") から panic("sync: negative WaitGroup counter") に変更されました。 この変更は、単語の選択に関するもので、WaitGroup の内部状態を指す際に「count」よりも「counter」の方がより適切であるという判断に基づいています。これにより、エラーメッセージの正確性と一貫性が向上します。

  3. テストの更新: src/pkg/sync/waitgroup_test.go 内の TestWaitGroupMisuse 関数で、期待されるパニックメッセージが新しいメッセージに合わせて更新されました。

    // 変更前
    // if err != "sync: negative WaitGroup count" {
    // 変更後
    if err != "sync: negative WaitGroup counter" {
    

    これは、コードの変更がテストによって適切に検証されることを保証するための標準的なプラクティスです。

コアとなるコードの変更箇所

diff --git a/src/pkg/sync/waitgroup.go b/src/pkg/sync/waitgroup.go
index 0165b1ffb2..bc9e738e78 100644
--- a/src/pkg/sync/waitgroup.go
+++ b/src/pkg/sync/waitgroup.go
@@ -32,10 +32,11 @@ type WaitGroup struct {
 
 // 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.
 func (wg *WaitGroup) Add(delta int) {
 	v := atomic.AddInt32(&wg.counter, int32(delta))
 	if v < 0 {
-		panic("sync: negative WaitGroup count")
+		panic("sync: negative WaitGroup counter")
 	}
 	if v > 0 || atomic.LoadInt32(&wg.waiters) == 0 {
 		return
diff --git a/src/pkg/sync/waitgroup_test.go b/src/pkg/sync/waitgroup_test.go
index 34430fc215..84c4cfc37a 100644
--- a/src/pkg/sync/waitgroup_test.go
+++ b/src/pkg/sync/waitgroup_test.go
@@ -50,7 +50,7 @@ func TestWaitGroup(t *testing.T) {
 func TestWaitGroupMisuse(t *testing.T) {
 	defer func() {
 		err := recover()
-		if err != "sync: negative WaitGroup count" {
+		if err != "sync: negative WaitGroup counter" {
 			t.Fatalf("Unexpected panic: %#v", err)
 		}
 	}()

コアとなるコードの解説

src/pkg/sync/waitgroup.go

// 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. // <-- 追加された行
func (wg *WaitGroup) Add(delta int) {
	v := atomic.AddInt32(&wg.counter, int32(delta))
	if v < 0 {
		panic("sync: negative WaitGroup counter") // <-- 変更された行
	}
	// ... (後続のコードは変更なし)
}
  • // If the counter goes negative, Add panics.: この行は、WaitGroup.Add メソッドのドキュメンテーションコメントに追加されました。これにより、WaitGroup のカウンタが負の値になった場合にパニックが発生するという、このメソッドの重要な挙動が明示的に示されます。これは、APIの利用者が予期せぬ挙動に遭遇するのを防ぎ、より堅牢なコードを書くのに役立ちます。
  • panic("sync: negative WaitGroup counter"): この行は、WaitGroup のカウンタが負の値(v < 0)になった場合に実行されるパニックメッセージを変更しています。以前は「count」という単語が使われていましたが、より正確な「counter」に変更されました。この変更は、エラーメッセージの明確性を高め、デバッグ時に問題の原因を特定しやすくすることを目的としています。

src/pkg/sync/waitgroup_test.go

func TestWaitGroupMisuse(t *testing.T) {
	defer func() {
		err := recover()
		if err != "sync: negative WaitGroup counter" { // <-- 変更された行
			t.Fatalf("Unexpected panic: %#v", err)
		}
	}()
	// ... (後続のコードは変更なし)
}
  • if err != "sync: negative WaitGroup counter": このテストコードは、WaitGroup.Add メソッドの誤用(カウンタが負になるケース)をテストしています。deferrecover() を使用してパニックを捕捉し、捕捉されたパニックメッセージが期待される文字列と一致するかどうかを検証しています。パニックメッセージが「sync: negative WaitGroup counter」に変更されたため、テストもそれに合わせて更新されました。これにより、コードの変更がテストによって正しく検証されていることが保証されます。

関連リンク

参考にした情報源リンク