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

[インデックス 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_startruntime.tstart)が、この m->mstartfn を呼び出すことで、Mの初期化と実際の処理を開始していました。

しかし、このアプローチにはいくつかの問題がありました。

  1. 初期化ロジックの分散: Mの初期化ロジックが newm の呼び出し元(sysmonstarttheworldstartm など)と、各OSのスレッド開始ルーチン、そして runtime.mstart に分散していました。これにより、Mのライフサイクル管理が複雑になり、バグの温床となる可能性がありました。
  2. スケーラビリティの問題: Mの初期化が複数の場所で行われると、ランタイムの変更やデバッグが困難になり、将来的なスケーラビリティに影響を与える可能性がありました。
  3. 仮定との乖離: 既存のランタイムコードの一部が、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_createCreateThreadclone など)を呼び出して新しいスレッドを生成し、そのスレッドが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 関数から helpgcspinning というブール引数が削除されました。これらの情報は、M構造体(m)のフィールドとして直接設定されるようになりました。 さらに重要なのは、newm が受け取る fn 引数が、Mが起動した直後に実行される「最初の関数」ではなく、runtime.mstart 内で呼び出される「追加の初期化関数」として扱われるようになった点です。この fnm->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 *gpvoid (*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: ランタイムの主要なプロシージャとスケジューリングロジックが含まれるファイル。newmruntime.mainruntime.starttheworldruntime.mstartstartmsysmon 関数が変更されています。
  • 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 から helpgcspinning 引数を削除し、Mの初期化に関する責任を runtime.mstart に集約しました。これにより、newm はMの作成とOSスレッドの起動に特化し、よりクリーンなインターフェースになりました。
  • runtime.mainruntime.starttheworld の変更: newm の呼び出しが簡素化され、sysmon やGCヘルパーMの起動が runtime.mstart を経由するようになりました。特に runtime.starttheworld では、GCヘルパーMを起動するために newm(mhelpgc, nil) が呼び出されています。これは、mhelpgcruntime.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のソースコード (特に src/runtime ディレクトリ)
  • Goのコミット履歴と関連するコードレビュー (CL: Change List)
  • Goランタイムの内部動作に関する技術ブログや解説記事 (一般的なGMPモデルの解説など)
  • Goの公式ドキュメント
  • GoのIssueトラッカー (関連するバグ報告や議論)
  • Goのメーリングリスト (golang-devなど)