[インデックス 11415] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/rpc
パッケージのベンチマークテスト (server_test.go
) におけるデータ競合(data race)を修正するものです。具体的には、client.Call
のエラー変数 err
のスコープが原因で発生していた競合状態を解消しています。
コミット
commit 290921bbb58514212f3d32a13e2de37cf4213b96
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Jan 26 20:06:27 2012 +0400
net/rpc: fix data race in benchmark
Fixes #2781.
R=golang-dev, rsc
CC=golang-dev, mpimenov
https://golang.org/cl/5577053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/290921bbb58514212f3d32a13e2de37cf4213b96
元コミット内容
net/rpc: fix data race in benchmark
Fixes #2781.
R=golang-dev, rsc
CC=golang-dev, mpimenov
https://golang.org/cl/5577053
変更の背景
このコミットは、Go言語の net/rpc
パッケージのベンチマークテスト server_test.go
内で発生していたデータ競合を修正するために行われました。コミットメッセージに Fixes #2781
とあるように、これはGoのIssueトラッカーで報告された問題 #2781 に対応するものです。データ競合は、複数のゴルーチンが同時に同じメモリ領域にアクセスし、少なくとも1つのアクセスが書き込みである場合に発生するバグの一種で、プログラムの予測不能な動作やクラッシュを引き起こす可能性があります。ベンチマークテストは通常、並行処理の性能を測定するために複数のゴルーチンを使用するため、このような競合状態が発生しやすい環境です。
前提知識の解説
データ競合 (Data Race)
データ競合とは、複数の並行実行される処理(Goにおいてはゴルーチン)が、同期メカニズムなしに同じメモリ位置にアクセスし、そのうち少なくとも1つのアクセスが書き込みである場合に発生するプログラミング上のバグです。データ競合が発生すると、プログラムの実行結果が非決定論的になり、デバッグが非常に困難になります。Go言語では、go run -race
コマンドを使用することで、データ競合を検出するツール(Race Detector)を有効にできます。
net/rpc
パッケージ
net/rpc
はGo言語の標準ライブラリで、ネットワーク越しにリモートプロシージャコール(RPC)を行うための機能を提供します。これにより、異なるプロセスや異なるマシン上で実行されているプログラム間で、関数呼び出しのように通信を行うことができます。クライアントはサーバー上のメソッドを呼び出し、サーバーはその結果をクライアントに返します。
ゴルーチン (Goroutine)
ゴルーチンはGo言語における軽量な並行実行単位です。go
キーワードの後に続く関数呼び出しによって新しいゴルーチンが起動され、その関数は他のゴルーチンと並行して実行されます。ゴルーチンはOSのスレッドよりもはるかに軽量であり、数千、数万のゴルーチンを同時に実行することが可能です。
sync/atomic
パッケージと atomic.AddInt32
sync/atomic
パッケージは、低レベルのアトミック(不可分)な操作を提供します。アトミック操作は、複数のゴルーチンから同時にアクセスされても、その操作全体が中断されることなく完了することが保証されます。
atomic.AddInt32(&N, -1)
は、N
という int32
型の変数に -1
をアトミックに加算する操作です。これは、複数のゴルーチンが同時にカウンタを減らすようなシナリオで、正確なカウントを保証するために使用されます。
client.Call
メソッド
net/rpc
パッケージの Client
型が提供する Call
メソッドは、リモートのRPCサーバー上のメソッドを呼び出すために使用されます。
client.Call("Arith.Add", args, reply)
は、RPCサーバー上の Arith
サービスに属する Add
メソッドを呼び出し、args
を引数として渡し、結果を reply
に格納します。このメソッドはエラーを返します。
技術的詳細
問題のコードは src/pkg/net/rpc/server_test.go
内の benchmarkEndToEnd
関数にあります。このベンチマーク関数は、複数のゴルーチンを起動してRPC呼び出しを並行して実行し、その性能を測定します。
元のコードは以下のようになっていました。
func benchmarkEndToEnd(dial func() (*Client, error), b *testing.B) {
// ...
go func() {
reply := new(Reply)
for atomic.AddInt32(&N, -1) >= 0 {
err = client.Call("Arith.Add", args, reply) // ここが問題
if err != nil {
b.Fatalf("rpc error: Add: expected no error but got string %q", err.Error())
}
}
}()
// ...
}
ここで問題となるのは、err
変数が go func() { ... }
の外側で宣言されており、複数のゴルーチン間で共有されている点です。err = client.Call(...)
の行では、client.Call
が返すエラー値を共有の err
変数に代入しています。複数のゴルーチンが同時にこの行を実行すると、err
変数への書き込みが競合し、データ競合が発生します。
GoのRace Detectorは、このような共有変数への非同期な書き込みを検出します。ベンチマークテストは通常、高い並行性で実行されるため、この問題が顕在化しやすかったと考えられます。
修正は非常にシンプルで、err
変数を各ゴルーチン内でローカルに宣言するように変更することです。
コアとなるコードの変更箇所
--- a/src/pkg/net/rpc/server_test.go
+++ b/src/pkg/net/rpc/server_test.go
@@ -518,7 +518,7 @@ func benchmarkEndToEnd(dial func() (*Client, error), b *testing.B) {\
go func() {\
reply := new(Reply)\
for atomic.AddInt32(&N, -1) >= 0{\
-\t\t\t\terr = client.Call(\"Arith.Add\", args, reply)\
+\t\t\t\terr := client.Call(\"Arith.Add\", args, reply)\
\t\t\t\tif err != nil {\
\t\t\t\t\tb.Fatalf(\"rpc error: Add: expected no error but got string %q\", err.Error())\
\t\t\t\t}\
コアとなるコードの解説
変更点は以下の1行のみです。
- 変更前:
err = client.Call("Arith.Add", args, reply)
- 変更後:
err := client.Call("Arith.Add", args, reply)
この変更により、err
変数の宣言が client.Call
の呼び出しと同時に行われ、そのスコープが for
ループ内の各イテレーション、またはより正確には、go func() { ... }
のクロージャ内でローカルになります。
具体的には、err := ...
はGoの短縮変数宣言であり、err
がまだ宣言されていない場合は新しい変数を宣言し、初期値を代入します。この場合、各ゴルーチンが go func() { ... }
を実行するたびに、そのゴルーチン専用の err
変数が作成されます。これにより、複数のゴルーチンがそれぞれ独立した err
変数を持つことになり、共有変数への競合する書き込みが解消され、データ競合が修正されます。
この修正は、並行処理における変数のスコープと共有の重要性を示す典型的な例です。共有されるべきでない変数をローカルスコープに限定することで、データ競合のような並行処理のバグを防ぐことができます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/290921bbb58514212f3d32a13e2de37cf4213b96
- Gerrit Change-ID (Goのコードレビューシステム): https://golang.org/cl/5577053
参考にした情報源リンク
- Go言語公式ドキュメント:
net/rpc
パッケージ: https://pkg.go.dev/net/rpcsync/atomic
パッケージ: https://pkg.go.dev/sync/atomictesting
パッケージ (ベンチマーク): https://pkg.go.dev/testing
- Go Race Detector: https://go.dev/blog/race-detector
- Go Issue #2781: Web検索では直接的な情報を見つけることができませんでしたが、コミットメッセージに明記されているため、過去にGoのIssueトラッカーに存在した問題であると推測されます。