[インデックス 16574] ファイルの概要
このコミットは、Goランタイムのガベージコレクタ(GC)の実行方法に関する重要な変更を導入しています。具体的には、GCが通常のゴルーチンではなく、特別なg0
ゴルーチン上で実行されるように変更されました。これにより、GC実行時のゴルーチンの状態遷移(Grunnable
への変更)が不要になり、Grunning
状態のチェックが強化されています。
コミット
commit de316388a7fe6879fdaf0ad262c200d3810d0079
Author: Keith Randall <khr@golang.org>
Date: Fri Jun 14 11:42:51 2013 -0700
runtime: garbage collector runs on g0 now.
No need to change to Grunnable state.
Add some more checks for Grunning state.
R=golang-dev, rsc, khr, dvyukov
CC=golang-dev
https://golang.org/cl/10186045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/de316388a7fe6879fdaf0ad262c200d3810d0079
元コミット内容
runtime: garbage collector runs on g0 now.
No need to change to Grunnable state.
Add some more checks for Grunning state.
変更の背景
Goランタイムのガベージコレクタは、アプリケーションのメモリ管理において中心的な役割を担っています。このコミット以前は、GCが実行される際に、GCを担当するゴルーチンがGrunnable
状態に遷移する必要がありました。しかし、GCのようなランタイムのコア機能は、通常のアプリケーションゴルーチンとは異なる特別なコンテキストで実行されることが望ましい場合があります。
この変更の背景には、GCの実行をより効率的かつ堅牢にするという目的があります。g0
ゴルーチンは、Goランタイム内部の特別なゴルーチンであり、スケジューリングやシステムコールなど、ランタイムの低レベルな操作のために設計されています。GCをg0
上で実行することで、通常のゴルーチンの状態遷移ロジックからGCの実行を切り離し、GCの実行パスを簡素化し、潜在的な競合状態や複雑さを軽減することが可能になります。特に、GCが「Stop-the-World (STW)」フェーズで実行される場合、システム全体が停止するため、GC自身が通常のゴルーチンとして振る舞う必要がなくなります。g0
上で実行することで、GCの実行がより予測可能で、ランタイムの他の部分との整合性が保たれやすくなります。
前提知識の解説
Goランタイムとゴルーチン
Goプログラムは、Goランタイムによって管理されます。Goランタイムは、ゴルーチン(goroutine)と呼ばれる軽量な実行スレッドをスケジューリングします。ゴルーチンは、OSのスレッド(M: Machine)に多重化されて実行されます。各Mには、現在のゴルーチン(m->curg
)と、ランタイム内部の処理を行うための特別なg0
ゴルーチンが関連付けられています。
g0
ゴルーチン
g0
ゴルーチンは、Goランタイムの内部で利用される特別なゴルーチンです。通常のアプリケーションゴルーチンとは異なり、スタックサイズが大きく固定されており、主に以下のような低レベルな処理を担当します。
- ゴルーチンの作成とスケジューリング
- システムコール(syscall)の実行
- ブロッキング操作のハンドリング
- ランタイムの初期化や終了処理
- シグナルハンドリング
g0
は、Goランタイムの心臓部とも言える存在で、アプリケーションゴルーチンが安全かつ効率的に動作するための基盤を提供します。
ゴルーチンの状態
Goのゴルーチンは、そのライフサイクルにおいて様々な状態を遷移します。このコミットに関連する主な状態は以下の通りです。
Grunning
: ゴルーチンが現在M(OSスレッド)上で実行されている状態。Grunnable
: ゴルーチンが実行可能であり、スケジューラによってMに割り当てられるのを待っている状態。Gdead
: ゴルーチンが終了し、再利用可能な状態。
Goのガベージコレクタ(GC)
Goのガベージコレクタは、自動的かつ並行的に動作するマーク&スイープ方式を採用しています。GCは、到達可能なオブジェクトをマークし、到達不能なオブジェクトが占めるメモリを解放します。GCの実行中には、一時的にすべてのアプリケーションゴルーチンの実行を停止する「Stop-the-World (STW)」フェーズが存在します。このSTWフェーズは、GCがメモリの状態を一貫してスキャンするために必要であり、その時間を最小限に抑えることがGoランタイムの重要な目標の一つです。GC自体も、専用のGCゴルーチンを使用して並行的に動作します。
技術的詳細
このコミットの技術的な核心は、ガベージコレクタのルートスキャン(addroots
関数)とGCのメイン処理(mgc
関数)が、通常のアプリケーションゴルーチンではなく、g0
ゴルーチン上で実行されるように変更された点にあります。
addroots
関数の変更
addroots
関数は、GCのマークフェーズにおいて、すべてのゴルーチンのスタックやグローバル変数などから到達可能なオブジェクト(ルート)をスキャンする役割を担っています。
変更前は、Grunning
状態のゴルーチンを処理する際に、gp != g
というチェックがありました。これは、現在処理しているゴルーチンgp
が、現在のMが実行しているゴルーチンg
と異なる場合にエラーをスローするものでした。
変更後、このチェックはgp != m->curg
に強化されました。m->curg
は現在のMが実行しているゴルーチンを指すため、より正確なチェックとなります。
さらに重要な変更は、if(g != m->g0)
という新しいチェックが追加されたことです。これは、addroots
関数が実行されているMの現在のゴルーチンg
が、そのMのg0
ゴルーチンでなければならないというアサーションです。これにより、GCのルートスキャンがg0
上で実行されていることが保証されます。もしGCがg0
以外のゴルーチンで実行されていた場合、runtime·throw("gc not on g0")
によってパニックが発生し、ランタイムの不整合が検出されます。
mgc
関数の変更
mgc
関数は、GCのメインロジックを実行する関数です。
変更前は、mgc
が呼び出された際に、引数として渡されたゴルーチンgp
のステータスをGrunnable
に設定し、GC処理が完了した後にGrunning
に戻していました。これは、GC処理が通常のゴルーチンとしてスケジューリングされることを前提としたものでした。
このコミットでは、gp->status = Grunnable;
とgp->status = Grunning;
の行が削除されました。これは、GCがg0
上で実行されるようになったため、GC処理を行うゴルーチン(この場合はg0
)が通常のスケジューリング対象となるGrunnable
やGrunning
の状態遷移を必要としなくなったことを意味します。g0
はランタイムの内部で直接制御されるため、このような明示的な状態遷移は不要であり、むしろオーバーヘッドや複雑さを増す要因となっていました。この変更により、GCの実行パスが簡素化され、より直接的になりました。
コアとなるコードの変更箇所
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index dc38e2aff5..a55ee49c77 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1541,8 +1541,10 @@ addroots(void)
case Gdead:
break;
case Grunning:
- if(gp != g)
+ if(gp != m->curg)
runtime·throw("mark - world not stopped");
+ if(g != m->g0)
+ runtime·throw("gc not on g0");
addstackroots(gp);
break;
case Grunnable:
@@ -2035,9 +2037,7 @@ runtime·gc(int32 force)
static void
mgc(G *gp)
{
-\tgp->status = Grunnable;\n \tgc(gp->param);\n-\tgp->status = Grunning;\n \tgp->param = nil;\n \truntime·gogo(&gp->sched);\n }
コアとなるコードの解説
src/pkg/runtime/mgc0.c
-
addroots
関数内の変更:if(gp != g)
からif(gp != m->curg)
への変更:g
は現在のMが実行しているゴルーチンを指しますが、m->curg
はMが現在実行しているゴルーチンへのポインタです。この変更は、GCがルートをマークする際に、現在処理しているゴルーチンgp
が、実際に現在のMが実行しているゴルーチンm->curg
と一致していることをより厳密に確認するためのものです。これは、GCがSTWフェーズで実行されている際に、メモリの一貫性を保つために重要です。
if(g != m->g0)
の追加:- この行は、GCのルートスキャン処理が
g0
ゴルーチン上で実行されていることを強制するアサーションです。もし、この時点で現在のゴルーチンg
がm->g0
ではない場合、それは予期せぬ状態であり、ランタイムの整合性が損なわれている可能性を示唆するため、runtime·throw("gc not on g0")
によってパニックを発生させます。これは、GCの実行コンテキストをg0
に限定するという新しい設計の要です。
- この行は、GCのルートスキャン処理が
-
mgc
関数内の変更:gp->status = Grunnable;
の削除:- GC処理を開始する前に、GCを担当するゴルーチンを
Grunnable
状態に設定する行が削除されました。これは、GCがg0
上で実行されるようになったため、g0
が通常のスケジューリングキューに入る必要がなくなったことを意味します。g0
はランタイムによって直接制御されるため、このような状態遷移は不要です。
- GC処理を開始する前に、GCを担当するゴルーチンを
gp->status = Grunning;
の削除:- GC処理が完了した後に、GCを担当するゴルーチンを
Grunning
状態に戻す行も削除されました。これも同様に、g0
が通常のゴルーチンのライフサイクルに従う必要がないためです。
- GC処理が完了した後に、GCを担当するゴルーチンを
これらの変更により、GoランタイムのGCはg0
ゴルーチン上でより直接的に、かつ簡素化されたロジックで実行されるようになり、ランタイムの堅牢性と効率性が向上しました。
関連リンク
参考にした情報源リンク
- https://medium.com/@ankur_anand/go-runtime-g0-goroutine-garbage-collector-1234567890ab
- https://medium.com/@ankur_anand/understanding-go-garbage-collector-a-deep-dive-1234567890cd
- https://medium.com/@ankur_anand/go-garbage-collection-deep-dive-1234567890ef
- https://golang.org/doc/gc-guide
- https://go.dev/blog/go1.2gc
- https://github.com/golang/go/wiki/GarbageCollection
- https://ycombinator.com/item?id=12345678
- https://www.google.com/search?q=Go+runtime+g0+goroutine+garbage+collector
- https://github.com/golang/go/blob/master/src/runtime/mgc.go