[インデックス 15532] ファイルの概要
コミット
commit e6a3e22c7569f7581f9a3dea3229ff6d0eda15e8
Author: Russ Cox <rsc@golang.org>
Date: Fri Mar 1 11:44:43 2013 -0500
runtime: start all threads with runtime.mstart
Putting the M initialization in multiple places will not scale.
Various code assumes mstart is the start already. Make it so.
R=golang-dev, devon.odell
CC=golang-dev
https://golang.org/cl/7420048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e6a3e22c7569f7581f9a3dea3229ff6d0eda15e8
元コミット内容
このコミットは、Goランタイムにおいて、すべてのOSスレッド(M)が runtime.mstart
関数から開始されるように変更することを目的としています。コミットメッセージには、「Mの初期化を複数の場所で行うことはスケールしない。様々なコードが mstart
が既に開始点であると仮定している。そうなるようにする。」と記載されています。
変更の背景
Goランタイムは、ゴルーチン(G)、論理プロセッサ(P)、OSスレッド(M)という3つの主要な抽象化を用いて並行性を管理しています。MはOSスレッドを表し、PはMがゴルーチンを実行するために必要なリソース(スケジューラキューなど)を提供します。
このコミット以前は、新しいOSスレッド(M)が作成される際に、その開始関数が newm
関数に引数として渡され、m->mstartfn
に格納されていました。そして、各OS固有のスレッド開始ルーチン(例: runtime.thr_start
や runtime.tstart
)が、この m->mstartfn
を呼び出すことで、Mの初期化と実際の処理を開始していました。
しかし、このアプローチにはいくつかの問題がありました。
- 初期化ロジックの分散: Mの初期化ロジックが
newm
の呼び出し元(sysmon
、starttheworld
、startm
など)と、各OSのスレッド開始ルーチン、そしてruntime.mstart
に分散していました。これにより、Mのライフサイクル管理が複雑になり、バグの温床となる可能性がありました。 - スケーラビリティの問題: Mの初期化が複数の場所で行われると、ランタイムの変更やデバッグが困難になり、将来的なスケーラビリティに影響を与える可能性がありました。
- 仮定との乖離: 既存のランタイムコードの一部が、Mが常に
runtime.mstart
から開始されることを暗黙的に仮定している箇所がありました。この仮定と実際の動作が異なることで、予期せぬ挙動やバグが発生するリスクがありました。
このコミットは、これらの問題を解決し、Mの初期化と開始ロジックを一元化することで、ランタイムの堅牢性と保守性を向上させることを目的としています。
前提知識の解説
Goランタイムのスケジューラ (GMPモデル)
Goランタイムは、GMPモデルと呼ばれるスケジューラを採用しています。
- G (Goroutine): 軽量な並行実行単位。Go言語の関数呼び出しがゴルーチンとして実行されます。
- M (Machine/OS Thread): オペレーティングシステムのスレッド。Goランタイムは、OSスレッドを抽象化したMを管理し、その上でゴルーチンを実行します。
- P (Processor/Logical Processor): 論理プロセッサ。Mがゴルーチンを実行するために必要なコンテキスト(ローカルな実行キュー、メモリ割り当てキャッシュなど)を提供します。Pの数は通常、CPUのコア数に設定され、同時に実行できるゴルーチンの数を制限します。
GMPモデルでは、MはPにアタッチされ、Pが持つローカルキューからGを取得して実行します。Mがシステムコールなどでブロックされると、そのMはPからデタッチされ、別のMがそのPにアタッチされてゴルーチンの実行を継続します。
runtime.mstart
関数
runtime.mstart
は、GoランタイムにおけるOSスレッド(M)の主要な開始ルーチンです。この関数は、Mが起動した後に実行されるべき初期化処理や、ゴルーチンをスケジューリングして実行するループを含んでいます。具体的には、Mの初期化(TLS設定、スタック設定など)、GCヘルパーとしての役割、そしてスケジューラループへの移行などが含まれます。
newosproc
関数
newosproc
は、新しいOSスレッドを作成するためのランタイム内部関数です。この関数は、OS固有のAPI(例: pthread_create
、CreateThread
、clone
など)を呼び出して新しいスレッドを生成し、そのスレッドがGoランタイムのコンテキストで実行を開始できるように設定します。
sysmon
(System Monitor)
sysmon
は、Goランタイムのシステムモニターゴルーチンです。これは特別なM上で実行され、GCのトリガー、アイドル状態のPの解放、ネットワークポーラーの実行など、ランタイムの健全性を維持するためのバックグラウンドタスクを担当します。
技術的詳細
このコミットの核心は、newm
関数と newosproc
関数のシグネチャと動作の変更、および各OS固有のスレッド開始ルーチンにおける mstartfn
の直接呼び出しの削除です。
newm
関数の変更
変更前:
static void newm(void(*fn)(void), P *p, bool helpgc, bool spinning);
変更後:
static void newm(void(*fn)(void), P *p);
newm
関数から helpgc
と spinning
というブール引数が削除されました。これらの情報は、M構造体(m
)のフィールドとして直接設定されるようになりました。
さらに重要なのは、newm
が受け取る fn
引数が、Mが起動した直後に実行される「最初の関数」ではなく、runtime.mstart
内で呼び出される「追加の初期化関数」として扱われるようになった点です。この fn
は m->mstartfn
に格納されます。
runtime.mstart
の変更
runtime.mstart
関数に以下のロジックが追加されました。
if(m->mstartfn)
m->mstartfn();
これにより、newm
に渡された関数(m->mstartfn
に格納された関数)は、runtime.mstart
の内部で呼び出されることが保証されます。つまり、すべてのMはまず runtime.mstart
を実行し、その中で必要に応じて追加の初期化関数を実行する、という統一されたフローになります。
newosproc
関数の変更
変更前:
void runtime·newosproc(M *mp, G *gp, void *stk, void (*fn)(void));
変更後:
void runtime·newosproc(M *mp, void *stk);
newosproc
関数から G *gp
と void (*fn)(void)
引数が削除されました。これは、newosproc
が新しいOSスレッドを作成する際に、そのスレッドが直接 fn
を実行するのではなく、常に runtime.mstart
を開始点として実行するように変更されたためです。
各OS固有の newosproc
の実装では、新しいスレッドの開始ルーチンとして直接 runtime.mstart
が指定されるようになりました。例えば、thread_darwin.c
では runtime·bsdthread_create
の呼び出しが以下のように変更されています。
変更前:
errno = runtime·bsdthread_create(stk, mp, gp, fn);
変更後:
errno = runtime·bsdthread_create(stk, mp, mp->g0, runtime·mstart);
これにより、OSが新しいスレッドを起動する際には、必ず runtime.mstart
がそのスレッドのエントリポイントとなります。
アセンブリコードの変更
各OS(FreeBSD, Windows)のアセンブリコード(.s
ファイル)も変更され、スレッドの開始ルーチンが m->mstartfn
を間接的に呼び出すのではなく、直接 runtime.mstart
を呼び出すようになりました。
例えば、sys_freebsd_386.s
では、以下の部分が変更されています。
変更前:
// newosproc left the function we should call in mp->mstartfn.
get_tls(CX)
MOVL m(CX), AX
MOVL m_mstartfn(AX), AX
CALL AX
変更後:
CALL runtime·mstart(SB)
これは、新しいOSスレッドが起動した際に、runtime.mstart
が直接呼び出されることを保証します。
sysmon
の変更
sysmon
関数内の runtime·asminit()
と runtime·minit()
の呼び出しが削除されました。これらの初期化は、runtime.mstart
内で統一的に行われるようになったため、sysmon
の開始時には不要になりました。
コアとなるコードの変更箇所
このコミットは、主にGoランタイムのスケジューリングとスレッド管理に関連する以下のファイルに影響を与えています。
src/pkg/runtime/proc.c
: ランタイムの主要なプロシージャとスケジューリングロジックが含まれるファイル。newm
、runtime.main
、runtime.starttheworld
、runtime.mstart
、startm
、sysmon
関数が変更されています。src/pkg/runtime/runtime.h
: ランタイムのヘッダーファイル。runtime.newosproc
の関数シグネチャが変更されています。src/pkg/runtime/sys_freebsd_386.s
,src/pkg/runtime/sys_freebsd_amd64.s
,src/pkg/runtime/sys_freebsd_arm.s
: FreeBSD向けの386, AMD64, ARMアーキテクチャのアセンブリコード。スレッド開始ルーチンが変更されています。src/pkg/runtime/sys_windows_386.s
,src/pkg/runtime/sys_windows_amd64.s
: Windows向けの386, AMD64アーキテクチャのアセンブリコード。スレッド開始ルーチンが変更されています。src/pkg/runtime/thread_darwin.c
,src/pkg/runtime/thread_freebsd.c
,src/pkg/runtime/thread_linux.c
,src/pkg/runtime/thread_netbsd.c
,src/pkg/runtime/thread_openbsd.c
,src/pkg/runtime/thread_plan9.c
,src/pkg/runtime/thread_windows.c
: 各OS固有のスレッド作成・管理ロジックが含まれるファイル。runtime.newosproc
の実装が変更されています。
コアとなるコードの解説
proc.c
における変更の意図
newm
関数の簡素化:newm
からhelpgc
とspinning
引数を削除し、Mの初期化に関する責任をruntime.mstart
に集約しました。これにより、newm
はMの作成とOSスレッドの起動に特化し、よりクリーンなインターフェースになりました。runtime.main
とruntime.starttheworld
の変更:newm
の呼び出しが簡素化され、sysmon
やGCヘルパーMの起動がruntime.mstart
を経由するようになりました。特にruntime.starttheworld
では、GCヘルパーMを起動するためにnewm(mhelpgc, nil)
が呼び出されています。これは、mhelpgc
がruntime.mstart
内で実行されることを意味します。runtime.mstart
の一元化: すべてのMがruntime.mstart
を通ることで、M固有の初期化(例:runtime·asminit()
やruntime·minit()
)や、GCヘルパーとしての役割、スピン状態の管理などが一箇所に集約され、ランタイムの動作が予測しやすくなりました。startm
の変更:startm
関数もnewm
の呼び出しを簡素化し、spinning
の状態をmspinning
関数を通じてm->mstartfn
に渡すように変更されました。
runtime.h
における変更の意図
runtime.newosproc
のシグネチャ変更は、この関数がOSスレッドの作成のみに責任を持ち、そのスレッドがどのGoランタイム関数から開始されるかという詳細を隠蔽するためのものです。これにより、OSスレッドの開始点が常にruntime.mstart
であるというランタイムの内部的な契約が強化されます。
各OS固有の thread_*.c
および sys_*.s
における変更の意図
- これらのファイルにおける
runtime.newosproc
の実装変更とアセンブリコードの修正は、新しいOSスレッドが起動する際に、OSが提供するスレッド開始APIに対して直接runtime.mstart
をエントリポイントとして指定することを保証します。これにより、Mの初期化パスが統一され、各OSプラットフォームでの挙動の一貫性が保たれます。
全体として、このコミットはGoランタイムのM(OSスレッド)のライフサイクル管理を大幅に簡素化し、初期化ロジックを一元化することで、ランタイムの堅牢性、保守性、およびスケーラビリティを向上させています。
関連リンク
- Goのスケジューラに関する公式ドキュメントやブログ記事 (当時のもの):
- Go Schedulers - The Go Programming Language (これは現在のドキュメントですが、当時の概念を理解するのに役立ちます)
- Goのソースコードリポジトリ:
参考にした情報源リンク
- Goのソースコード (特に
src/runtime
ディレクトリ) - Goのコミット履歴と関連するコードレビュー (CL: Change List)
- https://golang.org/cl/7420048 (このコミットのChange List)
- Goランタイムの内部動作に関する技術ブログや解説記事 (一般的なGMPモデルの解説など)
- Goの公式ドキュメント
- GoのIssueトラッカー (関連するバグ報告や議論)
- Goのメーリングリスト (golang-devなど)