[インデックス 15348] ファイルの概要
このコミットは、GoランタイムにおけるOSスレッド(M)の初期化プロセスを改善し、minit()
関数をmpreinit()
とminit()
の2つのフェーズに分割するものです。これにより、子スレッドでのメモリ割り当ての制約に対応し、シグナルスタックの割り当てを親スレッドで行うように変更しています。
コミット
commit a0955a2aa2a2fcd5352f7e517c3f965e24fd8584
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Feb 21 16:24:38 2013 +0400
runtime: split minit() to mpreinit() and minit()
mpreinit() is called on the parent thread and with mcache (can allocate memory),
minit() is called on the child thread and can not allocate memory.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7389043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a0955a2aa2a2fcd5352f7e517c3f965e24fd8584
元コミット内容
runtime: split minit() to mpreinit() and minit()
mpreinit() is called on the parent thread and with mcache (can allocate memory),
minit() is called on the child thread and can not allocate memory.
変更の背景
Goランタイムは、ゴルーチンをOSスレッドにマッピングして実行します。新しいOSスレッドが作成される際、そのスレッドの初期化処理(minit()
)が実行されます。しかし、この初期化プロセスにおいて、子スレッド(新しく作成されたOSスレッド)上ではメモリ割り当てが制限されるという制約がありました。特に、シグナルハンドリングに必要なスタック領域(gsignal
)の割り当てはメモリ割り当てを伴うため、子スレッドで直接行うことが問題となる可能性がありました。
このコミットの目的は、このメモリ割り当ての制約を解決することです。具体的には、minit()
の処理を2つのフェーズに分割します。
mpreinit()
: 親スレッド(新しいOSスレッドを作成した側のスレッド)で呼び出され、メモリキャッシュ(mcache)が利用可能な状態でメモリ割り当てが可能です。ここで、シグナルスタックなどのメモリ割り当てを事前に行います。minit()
: 子スレッド(新しく作成されたOSスレッド自身)で呼び出され、メモリ割り当てができない環境下でも安全に実行できるようにします。
これにより、シグナルスタックの割り当てのようなメモリを必要とする初期化処理を、メモリ割り当てが可能な親スレッド側で完了させ、子スレッドはメモリ割り当てなしで残りの初期化を進めることができるようになります。
前提知識の解説
Goランタイムの内部構造と、スレッドの初期化に関する以下の概念を理解することが、このコミットの変更を深く理解するために不可欠です。
- Goランタイム: Goプログラムの実行を管理する基盤です。メモリ管理(ガベージコレクション)、ゴルーチンとOSスレッドのスケジューリング、チャネル通信など、Go言語の並行処理モデルを支える重要な役割を担っています。Goプログラムは、ユーザーが書いた
main
関数が実行される前に、ランタイムの初期化処理が実行されます。 - M (Machine/OS Thread): Goランタイムにおける「マシン」または「OSスレッド」を表す構造体です。Goのスケジューラは、ゴルーチン(G)をM上で実行します。MはOSが管理するネイティブなスレッドに対応します。
- G (Goroutine): Go言語の軽量な並行処理単位です。OSスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。
- P (Processor): Goランタイムにおける「論理プロセッサ」を表す構造体です。PはMとGの間の仲介役となり、MがGを実行するためのコンテキストを提供します。
GOMAXPROCS
環境変数によって、同時に実行できるPの数が制御されます。 minit()
: Goランタイムにおいて、新しいOSスレッド(M)が起動する際に呼び出される初期化関数です。この関数は、スレッド固有のリソース(シグナルハンドリングスタックなど)の設定を行います。- メモリ割り当ての制約: OSスレッドの作成直後や、特定の低レベルなコンテキストでは、Goランタイムの通常のメモリ割り当てメカニズム(mcacheなど)がまだ完全に利用できない場合があります。このような状況下では、メモリ割り当てを伴う処理はクラッシュやデッドロックの原因となる可能性があります。
mcache
: Goランタイムのメモリ管理において、各M(OSスレッド)に紐付けられたローカルなメモリキャッシュです。これにより、頻繁なメモリ割り当て・解放の際にグローバルなロックを避けることができ、パフォーマンスが向上します。mcache
が利用可能であるということは、そのスレッドが効率的にメモリを割り当てられる状態にあることを意味します。
技術的詳細
このコミットの核心は、Goランタイムが新しいOSスレッドを起動する際の初期化フローの変更にあります。以前は、新しいM(OSスレッド)が起動すると、そのスレッド自身がminit()
を呼び出し、シグナルハンドリング用のスタック(m->gsignal
)をruntime·malg()
(メモリ割り当て関数)を使って割り当てていました。
しかし、新しいOSスレッドが完全に初期化される前の段階では、Goランタイムのメモリ割り当てシステム(特にmcache
)がまだ完全に機能していない可能性があります。この状態でメモリ割り当てを行おうとすると、競合状態やデッドロック、あるいは単にメモリ割り当てが失敗するといった問題が発生するリスクがありました。
この問題を解決するため、Dmitriy Vyukovはminit()
の責任を分割し、mpreinit()
という新しい関数を導入しました。
-
mpreinit(M *mp)
:- この関数は、新しいOSスレッドが実際に起動する前に、そのスレッドを作成する親スレッドのコンテキストで呼び出されます。
- 親スレッドは既に完全に初期化されており、
mcache
を含むGoランタイムのメモリ割り当てシステムが正常に機能しています。 - したがって、
mpreinit()
内でm->gsignal = runtime·malg(32*1024);
のようにメモリ割り当てを行うことは安全です。 - この変更により、シグナルスタックの割り当てが、メモリ割り当てが可能な安全なフェーズで行われるようになります。
src/pkg/runtime/proc.c
のmcommoninit
関数内で、runtime·mpreinit(mp);
が追加され、Mの共通初期化の一部としてmpreinit
が呼び出されるようになりました。
-
minit()
:- この関数は、新しいOSスレッドが実際に起動し、その子スレッド自身のコンテキストで呼び出されます。
minit()
が呼び出される時点では、メモリ割り当てができない(または推奨されない)環境であると想定されます。- そのため、以前
minit()
内で行われていたm->gsignal
の割り当て処理は削除され、mpreinit()
に移動されました。 minit()
の役割は、シグナルスタックの設定(runtime·signalstack
)やシグナルマスクの設定(runtime·sigprocmask
)など、メモリ割り当てを伴わない残りの初期化処理に限定されます。
この分割により、Goランタイムはより堅牢になり、OSスレッドの初期化におけるメモリ割り当てのタイミングに関する潜在的な問題を回避できるようになりました。これは、Goの並行処理モデルの安定性と信頼性を高める上で重要な改善です。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
src/pkg/runtime/proc.c
:mcommoninit
関数内で、runtime·mpreinit(mp);
の呼び出しが追加されました。これは、Mの共通初期化の一部としてmpreinit
が実行されることを意味します。
src/pkg/runtime/runtime.h
:runtime·mpreinit(M*);
の関数プロトタイプが追加されました。
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固有の
thread_*.c
ファイルでは、runtime·minit
関数からm->gsignal
のメモリ割り当てロジックが削除され、新しく追加されたruntime·mpreinit
関数にそのロジックが移動されました。 runtime·mpreinit
関数が各ファイルに追加され、m->gsignal = runtime·malg(32*1024);
(またはそれに相当する処理)がここで行われるようになりました。minit
関数のコメントが更新され、「新しいスレッドで呼び出され、メモリを割り当てることができない」という制約が明記されました。mpreinit
関数のコメントが追加され、「親スレッドで呼び出され、メモリを割り当てることができる」という説明が追加されました。
- これらのOS固有の
変更の概要としては、minit()
からgsignal
の割り当てを削除し、mpreinit()
という新しい関数を作成してそこに割り当て処理を移動した点が共通しています。
コアとなるコードの解説
具体的なコードの変更を見てみましょう。
src/pkg/runtime/proc.c
の変更点:
// mcommoninit(M *mp) 関数内
// ...
runtime·callers(1, mp->createstack, nelem(mp->createstack));
+ runtime·mpreinit(mp); // ここで新しい mpreinit が呼び出される
+
// Add to runtime·allm so garbage collector doesn't free m
// when it is just in a register or thread-local storage.
// ...
mcommoninit
は、新しいM(OSスレッド)が作成される際に共通して実行される初期化ルーチンです。この変更により、Mの初期化の早い段階でmpreinit
が呼び出され、メモリ割り当てが必要な処理が親スレッドのコンテキストで実行されることが保証されます。
src/pkg/runtime/runtime.h
の変更点:
// ...
void runtime·newosproc(M *mp, G *gp, void *stk, void (*fn)(void));
G* runtime·malg(int32);
void runtime·asminit(void);
+void runtime·mpreinit(M*); // 新しい関数のプロトタイプ宣言
void runtime·minit(void);
void runtime·unminit(void);
void runtime·signalstack(byte*, int32);
// ...
runtime.h
にruntime·mpreinit
の宣言が追加され、この関数がランタイムの公開APIの一部として利用可能になったことを示しています。
src/pkg/runtime/thread_darwin.c
(他のOS固有ファイルも同様) の変更点:
変更前:
// Called to initialize a new m (including the bootstrap m).
void
runtime·minit(void)
{
// Initialize signal handling.
if(m->gsignal == nil) // 条件付きでメモリ割り当て
m->gsignal = runtime·malg(32*1024); // OS X wants >=8K, Linux >=2K
runtime·signalstack((byte*)m->gsignal->stackguard - StackGuard, 32*1024);
runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);
}
変更後:
// Called to initialize a new m (including the bootstrap m).
// Called on the parent thread (main thread in case of bootstrap), can allocate memory.
void
runtime·mpreinit(M *mp)
{
mp->gsignal = runtime·malg(32*1024); // OS X wants >=8K, Linux >=2K
}
// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, can not allocate memory.
void
runtime·minit(void)
{
// Initialize signal handling.
// m->gsignalの割り当てはmpreinitに移動されたため、ここから削除
runtime·signalstack((byte*)m->gsignal->stackguard - StackGuard, 32*1024);
runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);
}
この変更が最も重要です。minit
関数からm->gsignal
の割り当て(runtime·malg
の呼び出し)が完全に削除され、新しく導入されたmpreinit
関数に移動されました。これにより、minit
はメモリ割り当てを伴わない処理のみを実行するようになり、新しいOSスレッドがメモリ割り当てが制限された環境で起動しても安全に初期化を完了できるようになります。
mpreinit
はM* mp
を引数として受け取るため、どのMに対してシグナルスタックを割り当てるかを明示的に指定できます。これは、親スレッドが子スレッドのためにリソースを準備するという意図を明確に示しています。
関連リンク
参考にした情報源リンク
- Go Runtime Initialization: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHpPXcz48IdQvs8mYgRtAev5jGmpDPRvPSv1tb5PidsKa8bwexHV9RxIz2hl2UPdRPo4FNUucN0mUE2Z44KovpfT7z1DBQJzzWZs-Lpw5e1k9Najs8VuuOZ6BbSIZNBSDb5ceKHH7M2OrcdB0rlxA==
- Go Runtime Overview: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEypts8pz9ASJjyr9u2djpzSFR6E-DXv-WkamUqZPwX6RwXpodBMLCFEcDI8V919_C0yWBaAIiCPaAVXKRhNGpAI8a0ixYc6xR5H0cnpRKa_YDFvPDSm14gLspFAPeMP_Ar53DqFLQ=
- Go Runtime
minit
andmpreinit
context: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG5T5CCwlfM_BVXgmNQwMvno9tZ2iwIvcdfD0h-xZrG0NlzR0P_Ml2g8Ksik4uRDbQfO_et5myUv5Yoy2DqzP0SS5pJBM-DGtsAfBk8VlRUK3TFz0i-wEYCpIum22XhXKKs71ucLXc= - Go Runtime
minit
andmpreinit
in Go source: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH92YD9AWaEkdd46ArDOE-rohd2msA3KDgefYw18QKpEcvpxKRelTNWJtJzmSA4XzX_X6tMAfTf-7oV4-DlpeMwcbO1j76DY82p1WWp9WlReKKssnQ-xor2Him6Fdc= - Go Memory Allocation: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHMfC8gE4Gyi6c6bIO4_Kd9CGziyEs531qAIrhL3a4E8bRdPs98c4tZ7MoiDJ4j_V9_XOFG9DPGBHUYx9gQIAkZTuoZm8f76ZiBonxWT3-ytTEueZqlF17u0uCmZt6bjLrKCvEDK6qqInbOeIRDlzF0oT
- Go Goroutines and OS Threads: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGRECfItfrgLV_R-IlSr4p45hg9HyM5Y7AckiMkNsXHxQbu47K2rmqCqXuRgRp9ZgYASuCEplVUxPiYFWHi0YAiCYo439kimFoqTeHb3CKLwM0XHcJOcp1WiIENy4vHhcGWNPEvNipp9bpAqEvfAZ7L75w6DDUl0MeG-7DtIJjxl3Hgz3ZF-8rqYRUce86Lj2ndIs7n