[インデックス 14637] ファイルの概要
このコミットは、Goランタイムのデータ競合検出器(race detector)のテストに関する修正です。具体的には、GOMAXPROCS
環境変数が1より大きい値に設定されている場合に、データ競合検出器のテストが正しく動作しない問題を解決することを目的としています。テストの実行環境において、GOMAXPROCS
の設定がテスト結果に影響を与えないように、環境変数の処理方法が変更されています。
コミット
commit b978995f5db06cc1b8f5a46adc38e4a30102d061
Author: Albert Strasheim <fullung@gmail.com>
Date: Thu Dec 13 10:14:32 2012 +0400
runtime/race: fix test for GOMAXPROCS>1
Fixes #4530.
R=dvyukov, dave
CC=golang-dev
https://golang.org/cl/6933052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b978995f5db06cc1b8f5a46adc38e4a30102d061
元コミット内容
runtime/race: fix test for GOMAXPROCS>1
Fixes #4530.
R=dvyukov, dave
CC=golang-dev
https://golang.org/cl/6933052
変更の背景
この変更は、Goのデータ競合検出器のテストが、GOMAXPROCS
環境変数が1より大きい値に設定されている場合に失敗するという問題(Issue #4530)を修正するために行われました。
Goのデータ競合検出器は、並行処理におけるデータ競合(複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つが書き込み操作である場合に発生する競合)を検出するための強力なツールです。この検出器のテストは、様々な競合パターンを意図的に発生させ、検出器がそれらを正しく報告するかどうかを確認します。
GOMAXPROCS
は、Goランタイムが同時に実行できるOSスレッドの最大数を制御する環境変数です。デフォルトでは、CPUの論理コア数に設定されます。GOMAXPROCS
が1の場合、Goランタイムは単一のOSスレッドでゴルーチンをスケジューリングします。しかし、GOMAXPROCS
が1より大きい場合、複数のOSスレッドが利用可能になり、ゴルーチンの並行実行がより活発になります。
問題の背景には、GOMAXPROCS
が1より大きい場合に、テストが意図しない動作をしたり、データ競合検出器が期待通りに報告しなかったりする可能性があったと考えられます。これは、テストが特定の並行実行パターンに依存しているか、あるいはGOMAXPROCS
の設定がテストのタイミングやスケジューリングに影響を与え、結果としてテストの信頼性を損なっていたためと考えられます。このコミットは、テストの実行環境からGOMAXPROCS
の設定を除外することで、テストの安定性と信頼性を向上させることを目的としています。
前提知識の解説
Goの並行処理とゴルーチン
Go言語は、軽量な並行処理の仕組みとして「ゴルーチン(goroutine)」を提供します。ゴルーチンは、OSスレッドよりもはるかに軽量な実行単位であり、数千、数万のゴルーチンを同時に実行することが可能です。Goランタイムは、これらのゴルーチンを少数のOSスレッドに多重化して実行します。
GOMAXPROCS
GOMAXPROCS
は、Goランタイムが同時に実行できるOSスレッドの最大数を設定する環境変数です。
GOMAXPROCS=1
: Goランタイムは単一のOSスレッドでゴルーチンをスケジューリングします。これにより、並行処理は行われますが、真の並列実行は発生しません(コンテキストスイッチによる擬似並列)。GOMAXPROCS > 1
: Goランタイムは複数のOSスレッドを利用し、複数のゴルーチンを同時に異なるCPUコアで実行することが可能になります。これにより、真の並列実行が実現され、マルチコアプロセッサの性能を最大限に活用できます。
この値は、Goプログラムのパフォーマンスに大きな影響を与える可能性があります。特にCPUバウンドな処理では、GOMAXPROCS
を適切に設定することでスループットが向上することが期待されます。
Goのデータ競合検出器 (Race Detector)
Goのデータ競合検出器は、Go 1.1で導入された強力なツールで、並行プログラムにおけるデータ競合を検出します。データ競合は、以下の3つの条件がすべて満たされた場合に発生します。
- 少なくとも2つのゴルーチンが同じメモリ位置にアクセスする。
- 少なくとも1つのアクセスが書き込み操作である。
- アクセスが同期メカニズムによって保護されていない。
データ競合は、プログラムの予測不能な動作、クラッシュ、または誤った結果を引き起こす可能性のある深刻なバグです。Goのデータ競合検出器は、実行時にこれらの競合を検出し、詳細なレポート(競合が発生した場所、スタックトレースなど)を提供します。
データ競合検出器を有効にするには、Goプログラムをビルドする際に-race
フラグを使用します。
例: go build -race myprogram.go
GORACE
環境変数
GORACE
環境変数は、Goのデータ競合検出器の動作を制御するための様々なオプションを提供します。このコミットで言及されているオプションは以下の通りです。
suppress_equal_stacks=0
: 通常、データ競合検出器は、同じスタックトレースを持つ重複する競合レポートを抑制(suppress)します。0
に設定することで、この抑制を無効にし、すべての競合レポートを報告するようにします。これは、テストにおいて、意図的に多数の競合を発生させ、それらがすべて検出されることを確認するために重要です。suppress_equal_addresses=0
: 同様に、同じメモリアドレスで発生する重複する競合レポートを抑制します。0
に設定することで、この抑制も無効にし、すべてのアドレスでの競合を報告するようにします。テストでは、同じメモリ領域を繰り返し使用して競合を発生させることが多いため、この設定も重要です。
これらのオプションを0
に設定することで、テストはデータ競合検出器が可能な限り多くの競合を報告するように強制し、テストの網羅性を高めます。
技術的詳細
このコミットの技術的な核心は、runtime/race
パッケージ内のテストヘルパー関数runTests()
が、テスト対象のGoプログラムを実行する際に、その環境変数からGOMAXPROCS
の設定を除外するように変更された点にあります。
runTests()
関数は、cmd.Env
スライスを構築して、テスト対象のプロセスに渡す環境変数を設定します。元のコードでは、os.Environ()
を使って現在の環境変数をすべて取得し、それにGORACE
の設定を追加していました。
// Original code
cmd.Env = append(os.Environ(), `GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0"`)
この方法では、テストを実行するシェル環境でGOMAXPROCS
が設定されている場合、その値がテスト対象のプロセスにも引き継がれていました。これが、GOMAXPROCS > 1
のときにテストが失敗する原因となっていました。
修正後のコードでは、os.Environ()
で取得した環境変数をループで処理し、GOMAXPROCS=
で始まる環境変数を除外しています。
// Modified code
for _, env := range os.Environ() {
if strings.HasPrefix(env, "GOMAXPROCS=") {
continue
}
cmd.Env = append(cmd.Env, env)
}
cmd.Env = append(cmd.Env, `GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0"`)
この変更により、runTests()
が起動するテストプロセスは、GOMAXPROCS
の設定に影響されずに実行されるようになります。これにより、テストの実行がより予測可能になり、GOMAXPROCS
の値に関わらず一貫した結果が得られるようになります。
なぜGOMAXPROCS
がテストに影響を与えたのかについては、具体的なテストケースの詳細は不明ですが、一般的にGOMAXPROCS
の値はゴルーチンのスケジューリングパターンに影響を与えます。例えば、GOMAXPROCS=1
ではゴルーチンは協調的に(yieldするまで)実行される傾向がありますが、GOMAXPROCS > 1
では真の並列実行により、より多くの競合パターンが露呈したり、あるいは特定のタイミングに依存するテストが失敗したりする可能性があります。この修正は、テストの実行環境を標準化し、GOMAXPROCS
による外部要因の影響を排除することで、テストの堅牢性を高めています。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/race/race_test.go
+++ b/src/pkg/runtime/race/race_test.go
@@ -146,7 +146,13 @@ func runTests() ([]byte, error) {
// The following flags turn off heuristics that suppress seemingly identical reports.
// It is required because the tests contain a lot of data races on the same addresses
// (the tests are simple and the memory is constantly reused).
-\tcmd.Env = append(os.Environ(), `GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0"`)
+\tfor _, env := range os.Environ() {
+\t\tif strings.HasPrefix(env, "GOMAXPROCS=") {
+\t\t\tcontinue
+\t\t}\n+\t\tcmd.Env = append(cmd.Env, env)
+\t}
+\tcmd.Env = append(cmd.Env, `GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0"`)
\tret, _ := cmd.CombinedOutput()\n \treturn ret, nil
}\n
コアとなるコードの解説
変更はsrc/pkg/runtime/race/race_test.go
ファイルのrunTests()
関数内で行われています。
元のコードでは、テスト対象のプロセスに渡す環境変数を設定するために、cmd.Env = append(os.Environ(), ...)
という行がありました。os.Environ()
は現在のプロセスのすべての環境変数を文字列のスライスとして返します。この方法では、テストを実行するシェル環境で設定されているGOMAXPROCS
の値がそのままテストプロセスに引き継がれていました。
修正後のコードでは、この部分が以下のように変更されています。
cmd.Env
スライスを初期化する前に、現在の環境変数(os.Environ()
)をループで一つずつ処理します。- ループ内で、各環境変数
env
が"GOMAXPROCS="
というプレフィックスで始まるかどうかをstrings.HasPrefix(env, "GOMAXPROCS=")
でチェックします。 - もし
GOMAXPROCS
で始まる環境変数が見つかった場合、continue
ステートメントによってその環境変数はスキップされ、cmd.Env
には追加されません。 GOMAXPROCS
以外のすべての環境変数は、cmd.Env = append(cmd.Env, env)
によってcmd.Env
スライスに追加されます。- ループが終了した後、以前と同様に
GORACE="suppress_equal_stacks=0 suppress_equal_addresses=0"
という設定がcmd.Env
に追加されます。
この変更により、runTests()
関数が起動するテストプロセスは、親プロセスのGOMAXPROCS
設定の影響を受けなくなります。これにより、GOMAXPROCS
の値が1より大きい場合でも、データ競合検出器のテストが安定して実行されるようになります。これは、テストの独立性と再現性を高める上で重要な修正です。
関連リンク
- Go言語公式ウェブサイト: https://go.dev/
- Goのデータ競合検出器に関する公式ドキュメント: https://go.dev/doc/articles/race_detector
- Goの
GOMAXPROCS
に関するドキュメント(runtime
パッケージ): https://pkg.go.dev/runtime#GOMAXPROCS
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goのデータ競合検出器に関する記事やブログポスト
- Goの
runtime
パッケージに関する情報