[インデックス 16950] ファイルの概要
このコミットは、Goランタイムのsrc/pkg/runtime/proc.c
ファイルに対する変更であり、以前のコミット(CL 12167043 / 475e11851fc1)によって導入された変更を元に戻す(undo)ことを目的としています。具体的には、sysmon
スレッドのパーキング(待機)ロジックと、SIGPROF
シグナル処理におけるスタックトレース生成ロジックの一部が、元の状態に戻されています。
コミット
commit 6ee69a97269eb26186d08832dcafd9432945f5ad
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed Jul 31 20:03:05 2013 +0400
undo CL 12167043 / 475e11851fc1
Submitted with some unrelated changes that were not intended to go in.
««« original CL description
runtime: do not park sysmon thread if any goroutines are running
Sysmon thread parks if no goroutines are running (runtime.sched.npidle == runtime.gomaxprocs).
Currently it's unparked when a goroutine enters syscall, it was enough
to retake P's from blocking syscalls.
But it's not enough for reliable goroutine preemption. We need to ensure that
sysmon runs if any goroutines are running.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/12167043
»»»
R=rsc
CC=golang-dev
https://golang.org/cl/12171044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6ee69a97269eb26186d08832dcafd9432945f5ad
元コミット内容
このコミットが元に戻した、以前のコミット(CL 12167043)の意図は以下の通りです。
runtime: do not park sysmon thread if any goroutines are running
Sysmon thread parks if no goroutines are running (runtime.sched.npidle == runtime.gomaxprocs).
Currently it's unparked when a goroutine enters syscall, it was enough
to retake P's from blocking syscalls.
But it's not enough for reliable goroutine preemption. We need to ensure that
sysmon runs if any goroutines are running.
要約すると、「sysmon
スレッドは、Goroutineが実行されていない場合に待機状態に入るが、Goroutineが実行されている場合は待機状態に入らないようにする。これは、システムコールに入ったGoroutineからPを再取得するだけでは不十分であり、Goroutineのプリエンプションの信頼性を確保するためには、Goroutineが実行されている限りsysmon
が動作し続ける必要がある」というものでした。
変更の背景
このコミットの背景は、コミットメッセージに明確に記載されています。「Submitted with some unrelated changes that were not intended to go in.」つまり、以前のコミット(CL 12167043 / 475e11851fc1)には、本来含まれるべきではない「無関係な変更」が含まれていました。
ソフトウェア開発において、特定の機能改善やバグ修正を目的としたコミットに、その目的とは直接関係のないコード変更が混入してしまうことは稀ではありません。このような場合、コードベースの整合性を保ち、意図しない副作用や将来的な問題を避けるために、一度その変更全体を元に戻し、必要な変更のみを改めてコミットし直すというプラクティスが取られます。
このコミットは、まさにその「クリーンアップ」のプロセスの一環として行われました。以前のコミットがsysmon
スレッドの挙動改善とGoroutineのプリエンプション信頼性向上を目指していたものの、その実装に不適切な部分があったため、一度その変更全体を破棄し、コードベースを安定した状態に戻すことが決定されたのです。
前提知識の解説
このコミットの変更内容を理解するためには、Goランタイムの以下の概念を理解しておく必要があります。
-
Goランタイム (Go Runtime): Goプログラムの実行を管理する中核部分です。これには、Goroutineスケジューラ、ガベージコレクタ (GC)、メモリ管理、システムコールインターフェースなどが含まれます。Goプログラムは、OSによって直接実行されるのではなく、このランタイム上で動作します。
-
Goroutine (ゴルーチン): Go言語における軽量な並行実行単位です。OSスレッドよりもはるかに軽量で、数百万のGoroutineを同時に実行することも可能です。GoroutineのスケジューリングはGoランタイムによって行われます。
-
P (Processor): Goスケジューラにおける「論理プロセッサ」または「コンテキスト」です。Goroutineを実行するためのリソース(実行キュー、ローカルなメモリキャッシュなど)を提供します。
GOMAXPROCS
環境変数によって、同時に実行可能なPの数が制御されます。各Pは1つのOSスレッド(M)にアタッチされ、その上でGoroutineを実行します。 -
M (Machine): OSスレッドを表します。MはPを保持し、そのP上でGoroutineを実行します。Mはシステムコールを実行したり、ブロッキング操作を行ったりする際にOSと直接やり取りします。
-
Sysmon (System Monitor) スレッド: Goランタイムの非常に重要なバックグラウンドスレッドです。その主な役割は以下の通りです。
- プリエンプション (Preemption): 長時間CPUを占有しているGoroutineを強制的に中断し、他のGoroutineにCPUを明け渡させることで、公平なスケジューリングを保証します。
- ネットワークポーラーの実行: ネットワークI/Oなどの非同期イベントを監視し、準備ができたGoroutineをウェイクアップします。
- ガベージコレクション (GC) のトリガー: GCの実行を定期的にチェックし、必要に応じてGCをトリガーします。
- アイドル状態のPの解放: 長時間アイドル状態のPをOSに解放し、リソースを節約します。
- デッドロックの検出: デッドロック状態にあるGoroutineを検出します。
sysmon
スレッドは、これらのタスクを定期的に実行するために、通常はスリープとウェイクアップを繰り返します。
-
プリエンプション (Preemption): GoroutineがCPUを長時間占有するのを防ぎ、他のGoroutineにも実行機会を与えるためのメカニズムです。Go 1.2以前のバージョンでは、プリエンプションは主に協調的(cooperative)であり、Goroutineが関数呼び出しやシステムコールなどの特定のポイントに到達したときにのみ発生しました。しかし、無限ループなどでこれらのポイントに到達しないGoroutineは、他のGoroutineの実行を妨げる可能性がありました。この時期(2013年)は、より信頼性の高い非同期プリエンプションの導入が模索されていた時期であり、
sysmon
スレッドの役割が注目されていました。 -
runtime.sched.sysmonwait
: Goランタイムのスケジューラ構造体runtime.sched
内のフラグで、sysmon
スレッドが現在待機状態にあるかどうかを示します。 -
runtime.notewakeup
: Goランタイム内部で使用される同期プリミティブの一つで、特定のイベントを待っているスレッド(この場合はsysmon
スレッド)をウェイクアップするために使用されます。 -
runtime.sigprof
: プロファイリングのためにOSからGoプロセスに送信されるSIGPROF
シグナルを処理するランタイム関数です。このシグナルは、CPUプロファイリングを行う際に、一定間隔でプログラムの実行状態(スタックトレースなど)をサンプリングするために使用されます。 -
runtime.gentraceback
: 現在実行中のGoroutineのスタックトレース(関数呼び出しの履歴)を生成するランタイム関数です。デバッグやプロファイリングにおいて重要な情報を提供します。
技術的詳細
このコミットは、以前のコミット(CL 12167043)によってsrc/pkg/runtime/proc.c
に導入された変更を完全に元に戻すものです。元に戻された変更は、主に以下の2つの領域に影響を与えていました。
-
sysmon
スレッドのウェイクアップロジックの変更:- 以前のコミットでは、
exitsyscallfast
とexitsyscall0
という2つの関数に、sysmon
スレッドを積極的にウェイクアップするロジックが追加されていました。 exitsyscallfast
は、Goroutineがシステムコールから高速パスで戻る際に呼び出されます。exitsyscall0
は、Goroutineがシステムコールから通常パスで戻る際に呼び出されます。- これらの関数内で、もしPがアイドル状態になったり、Goroutineがグローバル実行キューに戻されたりした際に、
runtime.sched.sysmonwait
フラグがセットされていれば(つまりsysmon
スレッドが待機状態であれば)、runtime.notewakeup(&runtime.sched.sysmonnote)
を呼び出してsysmon
スレッドを強制的にウェイクアップするようになっていました。 - この変更の意図は、
sysmon
スレッドがより頻繁に実行されるようにすることで、特にGoroutineのプリエンプションの機会を増やし、プリエンプションの信頼性を向上させることにありました。システムコールから戻ったGoroutineがCPUを再取得する際に、sysmon
がすぐに動作し、必要に応じてプリエンプションを実行できるようにするためです。 - この「undo」コミットでは、これらの追加された
sysmon
ウェイクアップロジックが削除され、元の挙動に戻されています。
- 以前のコミットでは、
-
runtime.sigprof
関数におけるスタックトレース生成ロジックの変更:- 以前のコミットでは、
runtime.sigprof
関数(プロファイリングシグナルを処理する関数)内で、スタックトレースを生成するかどうかを制御する複雑な条件分岐が導入されていました。 - 具体的には、
traceback
というブール変数が導入され、特定の条件下(例えば、Windows環境でない場合、m
やm->mcache
がnil
の場合、gp
がm->g0
やm->gsignal
の場合、m->racecall
がtrue
の場合など)でtraceback
がfalse
に設定され、スタックトレースの生成がスキップされる可能性がありました。 - また、
System
という空の関数が追加されていましたが、これはおそらくデバッグや特定のコンテキストでのみ使用されることを意図したもので、最終的には削除されています。 - この「undo」コミットでは、これらの
traceback
フラグに関連する条件分岐と、System
関数の定義が削除され、runtime.sigprof
関数はよりシンプルなスタックトレース生成ロジック(常にruntime.gentraceback
を呼び出す)に戻されています。これは、元の変更が意図しない副作用やバグを引き起こす可能性があったため、または単にその変更がこのコミットの目的とは無関係であったためと考えられます。
- 以前のコミットでは、
このコミットは、特定の機能改善(sysmon
の挙動とプリエンプション)を試みたものの、その実装が不完全であったり、意図しない変更を含んでいたために、一度その変更を破棄し、コードベースをクリーンな状態に戻すという、開発プロセスにおける健全なプラクティスを示しています。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/proc.c
ファイルのみで行われています。
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -1536,10 +1536,6 @@ exitsyscallfast(void)
if(runtime·sched.pidle) {
runtime·lock(&runtime·sched);
p = pidleget();
- if(p && runtime·atomicload(&runtime·sched.sysmonwait)) {
- runtime·atomicstore(&runtime·sched.sysmonwait, 0);
- runtime·notewakeup(&runtime·sched.sysmonnote);
- }
runtime·unlock(&runtime·sched);
if(p) {
acquirep(p);
@@ -1563,10 +1559,6 @@ exitsyscall0(G *gp)
p = pidleget();
if(p == nil)
globrunqput(gp);
- else if(runtime·atomicload(&runtime·sched.sysmonwait)) {
- runtime·atomicstore(&runtime·sched.sysmonwait, 0);
- runtime·notewakeup(&runtime·sched.sysmonnote);
- }
runtime·unlock(&runtime·sched);
if(p) {
acquirep(p);
@@ -1932,38 +1924,24 @@ static struct {
uintptr pcbuf[100];
} prof;
-static void
-System(void)
-{
-}
-
// Called if we receive a SIGPROF signal.
void
runtime·sigprof(uint8 *pc, uint8 *sp, uint8 *lr, G *gp)
{
int32 n;
- bool traceback;
- if(prof.fn == nil || prof.hz == 0)
- return;
- traceback = true;
// Windows does profiling in a dedicated thread w/o m.
if(!Windows && (m == nil || m->mcache == nil))
- traceback = false;
- if(gp == m->g0 || gp == m->gsignal)
- traceback = false;
- if(m != nil && m->racecall)
- traceback = false;
+\t\treturn;\n+\tif(prof.fn == nil || prof.hz == 0)\n+\t\treturn;\n
runtime·lock(&prof);
if(prof.fn == nil) {
runtime·unlock(&prof);
return;
}
- n = 1;
- prof.pcbuf[0] = (uintptr)pc;
- if(traceback)
- n = runtime·gentraceback((uintptr)pc, (uintptr)sp, (uintptr)lr, gp, 0, prof.pcbuf, nelem(prof.pcbuf), nil, nil, false);
+\tn = runtime·gentraceback((uintptr)pc, (uintptr)sp, (uintptr)lr, gp, 0, prof.pcbuf, nelem(prof.pcbuf), nil, nil, false);\n if(n > 0)
prof.fn(prof.pcbuf, n);
runtime·unlock(&prof);
コアとなるコードの解説
このコミットは、主に以下の3つのセクションでコードを削除しています。
-
exitsyscallfast
関数内の削除:- 削除されたコードは、Goroutineがシステムコールから高速に復帰する際に、アイドル状態のPが存在し、かつ
sysmon
スレッドが待機中(runtime.sched.sysmonwait
がセットされている)であれば、sysmon
スレッドをウェイクアップするロジックでした。 - 具体的には、
runtime·atomicstore(&runtime·sched.sysmonwait, 0);
でsysmonwait
フラグをクリアし、runtime·notewakeup(&runtime·sched.sysmonnote);
でsysmon
スレッドを通知して起こしていました。 - この削除により、システムコールからの復帰時に
sysmon
スレッドを強制的にウェイクアップする挙動が元に戻されました。
- 削除されたコードは、Goroutineがシステムコールから高速に復帰する際に、アイドル状態のPが存在し、かつ
-
exitsyscall0
関数内の削除:exitsyscall0
は、Goroutineがシステムコールから通常パスで復帰する際に呼び出される関数です。- ここでも同様に、
sysmon
スレッドが待機中であればウェイクアップするロジックが削除されました。 - この変更も、
sysmon
スレッドのウェイクアップに関する以前の挙動を復元するものです。
-
runtime·sigprof
関数内の削除と変更:System
という空の静的関数が削除されました。これは以前のコミットで追加されたもので、このコミットの目的とは無関係であったため削除されたと考えられます。runtime·sigprof
関数内では、traceback
というブール変数の宣言と、それを用いた複数の条件分岐が削除されました。- 以前のコードでは、
traceback
フラグに基づいてruntime·gentraceback
を呼び出すかどうかが制御されていました。例えば、Windows環境でない場合や、特定のMやGの状態(m->g0
,m->gsignal
,m->racecall
など)に応じて、スタックトレースの生成をスキップする可能性がありました。 - このコミットにより、これらの複雑な条件分岐が取り除かれ、
runtime·sigprof
は常にruntime·gentraceback
を呼び出してスタックトレースを生成する、よりシンプルなロジックに戻されました。これは、プロファイリングの際に常に完全なスタックトレースを取得できるようにするため、または以前の変更が意図しない挙動を引き起こす可能性があったためと考えられます。
これらの変更は、以前のコミットが導入したsysmon
スレッドの積極的なウェイクアップと、SIGPROF
処理におけるスタックトレース生成の複雑な条件分岐を元に戻すことで、Goランタイムのコードベースを、意図しない変更が混入する前の状態に復元することを目的としています。
関連リンク
- Goランタイムスケジューラに関する公式ドキュメント (Go 1.2時点の情報を含む可能性あり):
- Go Schedulers - The Go Programming Language (Go 1.2リリースノートのスケジューラに関する記述)
- Go's work-stealing scheduler (Go 1.2スケジューラに関するブログ記事)
- Goのプリエンプションに関する議論や進化の歴史:
- Go: Asynchronous Preemption (非同期プリエンプションの設計ドキュメント)
- Go: runtime: make goroutine preemption asynchronous (非同期プリエンプションに関するIssue)
参考にした情報源リンク
- Go source code (GitHub)
- Go official documentation
- Go blog
- Go Code Review Comments (Goのコードレビューに関する一般的なプラクティス)
- Go runtime: do not park sysmon thread if any goroutines are running (CL 12167043) (このコミットが元に戻した元の変更のGo Gerritリンク)
- Go runtime: undo CL 12167043 / 475e11851fc1 (CL 12171044) (このコミットのGo Gerritリンク)