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

[インデックス 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·gomaxprocsGOMAXPROCSの値)が決定された直後に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,4GOMAXPROCS=2 go test runtime -short -timeout=120s -cpu=1,2,4 に変更されました。
    • これは、ランタイムのテストを実行する際に、明示的にGOMAXPROCS環境変数を2に設定しています。これにより、複数のプロセッサを使用する環境でのランタイムの動作、特にこのコミットで修正されたGOMAXPROCSの初期化に関する問題が正しく解決されているかを確認するためのテストケースが強化されています。

これらの変更により、Goランタイムの初期化プロセスにおけるGOMAXPROCSの設定がより安全かつ正確に行われるようになり、特定の競合状態が解消されました。

関連リンク

参考にした情報源リンク