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

[インデックス 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)が通常のスケジューリング対象となるGrunnableGrunningの状態遷移を必要としなくなったことを意味します。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ゴルーチン上で実行されていることを強制するアサーションです。もし、この時点で現在のゴルーチンgm->g0ではない場合、それは予期せぬ状態であり、ランタイムの整合性が損なわれている可能性を示唆するため、runtime·throw("gc not on g0")によってパニックを発生させます。これは、GCの実行コンテキストをg0に限定するという新しい設計の要です。
  • mgc関数内の変更:

    • gp->status = Grunnable; の削除:
      • GC処理を開始する前に、GCを担当するゴルーチンをGrunnable状態に設定する行が削除されました。これは、GCがg0上で実行されるようになったため、g0が通常のスケジューリングキューに入る必要がなくなったことを意味します。g0はランタイムによって直接制御されるため、このような状態遷移は不要です。
    • gp->status = Grunning; の削除:
      • GC処理が完了した後に、GCを担当するゴルーチンをGrunning状態に戻す行も削除されました。これも同様に、g0が通常のゴルーチンのライフサイクルに従う必要がないためです。

これらの変更により、GoランタイムのGCはg0ゴルーチン上でより直接的に、かつ簡素化されたロジックで実行されるようになり、ランタイムの堅牢性と効率性が向上しました。

関連リンク

参考にした情報源リンク