[インデックス 12393] ファイルの概要
このコミットは、GoランタイムにおけるGOMAXPROCS
の設定タイミングに関する修正です。具体的には、メインゴルーチンが開始される前にGOMAXPROCS
を設定するのではなく、メインゴルーチンが開始された後に設定するように変更することで、特定の競合状態を解消しています。
コミット
commit aa1aaee7fd96a76e595add58b9889b4cd6703d3a
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Mon Mar 5 16:40:27 2012 -0500
runtime: wait for main goroutine before setting GOMAXPROCS.
Fixes #3182.
R=golang-dev, dvyukov, rsc
CC=golang-dev, remy
https://golang.org/cl/5732057
---
src/pkg/runtime/proc.c | 6 +++++-\n src/run.bash | 4 ++--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/pkg/runtime/proc.c b/src/pkg/runtime/proc.c
index de7090c527..88e2b61388 100644
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -200,7 +200,9 @@ runtime·schedinit(void)
tn = maxgomaxprocs;
runtime·gomaxprocs = n;
}\n-\tsetmcpumax(runtime·gomaxprocs);\n+\t// wait for the main goroutine to start before taking
+\t// GOMAXPROCS into account.
+\tsetmcpumax(1);\n \truntime·singleproc = runtime·gomaxprocs == 1;
canaddmcpu(); // mcpu++ to account for bootstrap m
@@ -225,6 +227,8 @@ runtime·main(void)
// by calling runtime.LockOSThread during initialization
// to preserve the lock.
runtime·LockOSThread();
+\t// From now on, newgoroutines may use non-main threads.
+\tsetmcpumax(runtime·gomaxprocs);\n \truntime·sched.init = true;
\tscvg = runtime·newproc1((byte*)runtime·MHeap_Scavenger, nil, 0, 0, runtime·main);
\tmain·init();
diff --git a/src/run.bash b/src/run.bash
index fd3b1f27b7..fdbf47663b 100755
--- a/src/run.bash
+++ b/src/run.bash
@@ -26,8 +26,8 @@ echo '# Testing packages.'
time go test std -short -timeout=120s
echo
-echo '# runtime -cpu=1,2,4'
-go test runtime -short -timeout=120s -cpu=1,2,4
+echo '# GOMAXPROCS=2 runtime -cpu=1,2,4'
+GOMAXPROCS=2 go test runtime -short -timeout=120s -cpu=1,2,4
echo
echo '# sync -cpu=10'
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aa1aaee7fd96a76e595add58b9889b4cd6703d3a
元コミット内容
runtime: wait for main goroutine before setting GOMAXPROCS.
Fixes #3182.
R=golang-dev, dvyukov, rsc CC=golang-dev, remy https://golang.org/cl/5732057
変更の背景
このコミットは、GoランタイムにおけるGOMAXPROCS
の設定タイミングに関するバグ(Issue #3182)を修正するために導入されました。GOMAXPROCS
は、Goプログラムが同時に実行できるOSスレッドの最大数を制御する環境変数または関数です。
問題の根源は、runtime·schedinit
関数(スケジューラの初期化)内でGOMAXPROCS
が設定される際に、まだメインゴルーチンが完全に起動していない状況で、setmcpumax
が呼び出されていたことにあります。これにより、一部のシステムや特定の条件下で、スケジューラが正しく初期化されず、プログラムの動作が不安定になる可能性がありました。特に、GOMAXPROCS
が1より大きい値に設定されている場合に、この問題が顕在化しやすかったと考えられます。
この修正の目的は、GOMAXPROCS
の値を実際にスケジューラに反映させるタイミングを、メインゴルーチンが起動し、システムが安定した状態になってからに遅らせることで、初期化時の競合状態を回避し、ランタイムの堅牢性を向上させることです。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理するシステムです。これには、ガベージコレクション、スケジューリング、メモリ管理などが含まれます。
- ゴルーチン (Goroutine): Goにおける軽量な並行実行単位です。OSスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行することも可能です。GoランタイムがゴルーチンをOSスレッドにマッピングし、スケジューリングを行います。
- GOMAXPROCS: Goプログラムが同時に実行できるOSスレッドの最大数を設定する環境変数、または
runtime.GOMAXPROCS
関数です。デフォルトでは、利用可能なCPUコア数に設定されます。この値は、GoランタイムのスケジューラがゴルーチンをOSスレッドに割り当てる方法に影響を与えます。例えば、GOMAXPROCS=1
の場合、Goプログラムは同時に1つのOSスレッドしか使用せず、真の並列実行は行われません(並行実行は可能)。 - スケジューラ (Scheduler): Goランタイムの一部で、ゴルーチンをOSスレッドに効率的に割り当てる役割を担います。これにより、多数のゴルーチンが限られた数のOSスレッド上で並行して実行されます。
runtime·schedinit
: Goランタイムのスケジューラを初期化する関数です。プログラムの起動時に一度だけ呼び出されます。runtime·main
: Goプログラムのエントリポイントとなるメインゴルーチンが実行される関数です。setmcpumax
: Goランタイム内部の関数で、スケジューラが使用できるCPU(OSスレッド)の最大数を設定します。runtime.LockOSThread()
: 現在のゴルーチンを現在のOSスレッドにロックする関数です。これにより、そのゴルーチンは他のOSスレッドに移動しなくなります。
技術的詳細
このコミットの技術的な核心は、GOMAXPROCS
の値をスケジューラに反映させるタイミングの変更です。
修正前は、runtime·schedinit
関数内でruntime·gomaxprocs
(GOMAXPROCS
の値)が決定された直後にsetmcpumax(runtime·gomaxprocs)
が呼び出されていました。この時点では、メインゴルーチンを含む他のゴルーチンがまだ完全に初期化され、実行可能な状態になっていない可能性がありました。
修正後は、runtime·schedinit
内では一時的にsetmcpumax(1)
が呼び出されます。これは、初期化フェーズではGoランタイムが1つのOSスレッドのみを使用するように制限することを意味します。これにより、初期化プロセス中の競合状態や予期せぬ動作を防ぎます。
そして、メインゴルーチンが起動し、runtime·main
関数内でruntime·LockOSThread()
が呼び出された後、つまりメインゴルーチンがOSスレッドにロックされ、システムがより安定した状態になった後に、改めてsetmcpumax(runtime·gomaxprocs)
が呼び出されます。このタイミングで、GOMAXPROCS
の本来の値がスケジューラに適用され、新しいゴルーチンが複数のOSスレッドを利用できるようになります。
この変更により、Goランタイムの初期化プロセスがより堅牢になり、GOMAXPROCS
の設定が意図した通りに機能することが保証されます。
また、src/run.bash
の変更は、この修正が正しく機能するかどうかをテストするためのものです。GOMAXPROCS=2
を設定してgo test runtime
を実行することで、複数のプロセッサを使用する環境でのランタイムの動作を検証しています。
コアとなるコードの変更箇所
src/pkg/runtime/proc.c
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -200,7 +200,9 @@ runtime·schedinit(void)
tn = maxgomaxprocs;
runtime·gomaxprocs = n;
}
-\tsetmcpumax(runtime·gomaxprocs);\n+\t// wait for the main goroutine to start before taking
+\t// GOMAXPROCS into account.
+\tsetmcpumax(1);\n \truntime·singleproc = runtime·gomaxprocs == 1;
canaddmcpu(); // mcpu++ to account for bootstrap m
@@ -225,6 +227,8 @@ runtime·main(void)
// by calling runtime.LockOSThread during initialization
// to preserve the lock.
runtime·LockOSThread();
+\t// From now on, newgoroutines may use non-main threads.
+\tsetmcpumax(runtime·gomaxprocs);\n \truntime·sched.init = true;
\tscvg = runtime·newproc1((byte*)runtime·MHeap_Scavenger, nil, 0, 0, runtime·main);
\tmain·init();
src/run.bash
--- a/src/run.bash
+++ b/src/run.bash
@@ -26,8 +26,8 @@ echo '# Testing packages.'
time go test std -short -timeout=120s
echo
-echo '# runtime -cpu=1,2,4'
-go test runtime -short -timeout=120s -cpu=1,2,4
+echo '# GOMAXPROCS=2 runtime -cpu=1,2,4'
+GOMAXPROCS=2 go test runtime -short -timeout=120s -cpu=1,2,4
echo
echo '# sync -cpu=10'
コアとなるコードの解説
src/pkg/runtime/proc.c
の変更
runtime·schedinit
関数内:- 変更前:
setmcpumax(runtime·gomaxprocs);
が直接呼び出されていました。これは、GOMAXPROCS
の値が決定された直後に、その値に基づいてスケジューラが使用するCPU数を設定していました。 - 変更後:
setmcpumax(1);
に変更されました。これにより、スケジューラの初期化段階では、一時的に1つのOSスレッドのみを使用するように制限されます。コメント// wait for the main goroutine to start before taking // GOMAXPROCS into account.
が追加され、この変更の意図が明確にされています。
- 変更前:
runtime·main
関数内:runtime·LockOSThread();
の呼び出し後、setmcpumax(runtime·gomaxprocs);
が追加されました。これは、メインゴルーチンが起動し、OSスレッドにロックされた後、つまりシステムがより安定した状態になった後に、GOMAXPROCS
の本来の値をスケジューラに適用することを意味します。コメント// From now on, newgoroutines may use non-main threads.
が追加され、この時点から新しいゴルーチンが複数のスレッドを利用できるようになることが示されています。
src/run.bash
の変更
go test runtime -short -timeout=120s -cpu=1,2,4
がGOMAXPROCS=2 go test runtime -short -timeout=120s -cpu=1,2,4
に変更されました。- これは、ランタイムのテストを実行する際に、明示的に
GOMAXPROCS
環境変数を2に設定しています。これにより、複数のプロセッサを使用する環境でのランタイムの動作、特にこのコミットで修正されたGOMAXPROCS
の初期化に関する問題が正しく解決されているかを確認するためのテストケースが強化されています。
- これは、ランタイムのテストを実行する際に、明示的に
これらの変更により、Goランタイムの初期化プロセスにおけるGOMAXPROCS
の設定がより安全かつ正確に行われるようになり、特定の競合状態が解消されました。
関連リンク
- Go Issue #3182: https://github.com/golang/go/issues/3182 (このコミットが修正した問題のトラッキング)
- Go CL 5732057: https://golang.org/cl/5732057 (このコミットに対応するGoのコードレビュー)
参考にした情報源リンク
- Goの公式ドキュメント (runtimeパッケージ): https://pkg.go.dev/runtime
- Goのソースコード (runtime/proc.c): https://github.com/golang/go/blob/master/src/runtime/proc.go (現在のGoのソースコードはproc.cではなくproc.goに相当する部分があります)
- GoのGOMAXPROCSに関する解説記事 (例: The Go scheduler): https://go.dev/blog/go11sched (Goスケジューラの進化に関する公式ブログ記事)