[インデックス 13384] ファイルの概要
このコミットは、Goランタイムにおける潜在的なガベージコレクション(GC)のデッドロックを修正するものです。具体的には、GC処理中にメモリ割り当てが行われる際に、GCが一時的に無効化されるようにm->locks
カウンタを操作することで、競合状態を防ぎます。
コミット
- コミットハッシュ:
902911bcff91127cd8c5c7e10d200eb97f0c1893
- Author: Dmitriy Vyukov dvyukov@google.com
- Date: Mon Jun 25 11:08:09 2012 +0400
- コミットメッセージ:
runtime: fix potential GC deadlock The issue seems to not be triggered right now, but I've seen the deadlock after some other legal modifications to runtime. So I think we are safer this way. R=rsc, r CC=golang-dev https://golang.org/cl/6339051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/902911bcff91127cd8c5c7e10d200eb97f0c1893
元コミット内容
runtime: fix potential GC deadlock
The issue seems to not be triggered right now,
but I've seen the deadlock after some other legal
modifications to runtime.
So I think we are safer this way.
R=rsc, r
CC=golang-dev
https://golang.org/cl/6339051
変更の背景
この変更は、Goランタイムのガベージコレクション(GC)プロセスにおける潜在的なデッドロックを解消するために導入されました。コミットメッセージによると、このデッドロックは現在のコードベースでは直接トリガーされないものの、ランタイムに対する他の正当な変更を加えた後に発生する可能性があることがDmitriy Vyukov氏によって確認されています。これは、Goランタイムのような低レベルの並行処理システムでは、一見無関係な変更が既存の同期メカニズムに予期せぬ影響を与え、競合状態やデッドロックを引き起こす可能性があることを示唆しています。そのため、将来的な安定性と堅牢性を確保するために、この予防的な修正が適用されました。
前提知識の解説
Goのガベージコレクション (GC)
Go言語は、自動メモリ管理のためにガベージコレクタ(GC)を採用しています。GoのGCは、主に並行マーク&スイープ方式で動作し、プログラムの実行と並行して不要なメモリを回収します。GCサイクル中には、一時的にすべてのゴルーチン(Goの軽量スレッド)の実行を停止する「Stop-the-World (STW)」フェーズが存在します。このSTWフェーズは、GCがメモリの状態を正確に把握し、一貫性を保つために不可欠ですが、その時間は最小限に抑えるように設計されています。
デッドロック
デッドロックとは、複数のプロセスやスレッドが互いに相手が保持しているリソースの解放を待ち続け、結果としてどのプロセスも処理を進められなくなる状態を指します。並行プログラミングにおいて、共有リソースへのアクセスを同期するためにロック(ミューテックスなど)が使用されますが、ロックの取得順序の不一致や、ロックの解放忘れなどによってデッドロックが発生する可能性があります。Goランタイムのような複雑なシステムでは、内部的なロックメカニズムが多数存在し、それらの相互作用によってデッドロックが発生することがあります。
m.locks
と Goランタイムの M
(Machine) 構造体
Goランタイムの内部には、OSスレッドを表すM
(Machine)という構造体が存在します。このM
構造体には、locks
というフィールドが含まれています。m.locks
は、そのOSスレッドが現在保持しているランタイム内部のロックの数を追跡するためのカウンタです。このカウンタは、特定の重要なランタイム操作(例えば、GCの実行中など)において、そのOSスレッドがGCによって停止されるべきではないことを示すために使用されることがあります。m.locks
が0より大きい場合、そのOSスレッドはGCのSTWフェーズ中に停止されない、あるいは特定のGC関連の処理がブロックされる可能性があります。これは、GCがメモリを操作している最中に、別のスレッドがそのメモリを書き換えることを防ぐための重要な同期メカニズムの一部です。
技術的詳細
このコミットは、src/pkg/runtime/mgc0.c
ファイル内のruntime·gc
関数、すなわちGoのガベージコレクションの主要なエントリポイントに修正を加えています。
問題の核心は、GCプロセス中にruntime·parforalloc
という関数が呼び出される点にあります。parforalloc
は、並行処理のためのメモリを割り当てる関数であり、この割り当て処理自体がGCの対象となる可能性のあるヒープメモリを操作します。
GCが実行されている最中に、GC自身がメモリを割り当てようとすると、GCの内部状態とメモリ割り当ての間に競合状態が発生し、デッドロックを引き起こす可能性があります。特に、GCがメモリをマークまたはスイープしている最中に、parforalloc
が新しいメモリを要求し、そのメモリがGCの現在のフェーズと矛盾する状態にある場合、問題が生じます。
この修正では、runtime·parforalloc
が呼び出される直前にm->locks++
を実行し、その直後にm->locks--
を実行しています。
m->locks++
: これは、現在のOSスレッド(m
)がランタイム内部のロックを一つ取得したことを示します。これにより、GCがこのスレッドを停止しようとしたり、このスレッドがメモリを割り当てる際にGCの特定の側面がブロックされたりする可能性があります。このコンテキストでは、「GC中にparforalloc
でのメモリ割り当てを無効にする」というコメントが示唆するように、GCがparforalloc
のメモリ割り当て処理と競合しないように、一時的にGCの特定の側面を抑制する効果があります。m->locks--
:parforalloc
の処理が完了した後、ロックを解放したことを示します。
この操作により、parforalloc
が実行されている間、GCは特定の制約の下で動作するか、あるいは一時的にそのスレッドに対するGCの介入が抑制されます。これにより、GCとメモリ割り当ての間のデッドロックの可能性が排除され、ランタイムの安定性が向上します。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -905,12 +905,14 @@ runtime·gc(int32 force)
work.debugmarkdone = 0;
work.nproc = runtime·gcprocs();
addroots();
+\tm->locks++; // disable gc during mallocs in parforalloc
if(work.markfor == nil)
work.markfor = runtime·parforalloc(MaxGcproc);
runtime·parforsetup(work.markfor, work.nproc, work.nroot, nil, false, markroot);
if(work.sweepfor == nil)
work.sweepfor = runtime·parforalloc(MaxGcproc);
runtime·parforsetup(work.sweepfor, work.nproc, runtime·mheap.nspan, nil, true, sweepspan);
+\tm->locks--;
if(work.nproc > 1) {
runtime·noteclear(&work.alldone);
runtime·helpgc(work.nproc);
コアとなるコードの解説
変更はsrc/pkg/runtime/mgc0.c
ファイルのruntime·gc
関数内で行われています。
-
m->locks++; // disable gc during mallocs in parforalloc
:- この行は、
runtime·parforalloc
が呼び出される直前に挿入されています。 m
は現在のOSスレッド(Machine)を表すポインタです。m->locks
カウンタをインクリメントすることで、現在のスレッドがランタイム内部のロックを保持している状態にします。- コメントにあるように、これは
parforalloc
内でのメモリ割り当て(mallocs
)中にGCが介入するのを一時的に「無効にする」ことを目的としています。これにより、GCがメモリをスキャンまたは操作している最中に、parforalloc
がそのメモリ構造を変更しようとすることによる競合状態やデッドロックを防ぎます。
- この行は、
-
m->locks--;
:- この行は、
runtime·parforalloc
の呼び出し(work.markfor
とwork.sweepfor
の両方に対する設定を含む)が完了した直後に挿入されています。 m->locks
カウンタをデクリメントすることで、先ほど取得した「ロック」を解放します。- これにより、
parforalloc
のクリティカルセクションが終了し、GCがこのスレッドに対して通常の動作を再開できるようになります。
- この行は、
このペアの操作は、parforalloc
が実行される間だけ、GCの特定の側面を抑制する非常に短い期間のクリティカルセクションを作成します。これは、GCとメモリ割り当ての間の同期を確保し、潜在的なデッドロックを回避するための重要なメカニズムです。
関連リンク
- Go CL 6339051: https://golang.org/cl/6339051
参考にした情報源リンク
- Go runtime GC deadlock m.locks: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHpD10wS4CTkQ32Qbtck395B3574LFob1PlQ3fvigEyWvshHkNzfCBrBtPAxbvMTKDI4zZWMgZGqrSRIhIYmSrJuc1U2jmRtE9-PxPivIdPnDxarGpetid1rAmYcfbAA9BoIE1U
- Goにおけるデッドロックの概念: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFLsQJV1MVBjjn_6zbAyCxHHWesaO-ttu8EvJH-i2sQA8hUPZjxtA1m-VEZ_5WwTYa50rjccJctFZhMabuGlg_dONFuTh-4fJhV1Fbn1DtxUMDy3cg9E0u1IEXQpv9HHjnrAQs1sa0BDv4QRGtp9eLRmipQ_VbQYx-5-A3gTAMCWhz4btH66YtEyJeynBETTGrvSWlywsuJxgaz60GjEAbW3IvnD7vTkzU28xd5yQ==
- Goランタイムの
M
構造体に関する情報: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEUbfZ4qwECLuImEjnUDsdo1xwXyDokRbZk7dBKiazU2IaX3fuNXYyNUcu0rocJACod2W1cdQU6Z-67OrS430p--_xNZEfjWvVR-Dd1yiuvaHpopbKSs17qxCFDEXUy3HCprp_GvS_kBnh6vW-_SKEjiOo3xMl0wGbxR5M=