[インデックス 18868] ファイルの概要
このコミットは、Goランタイムのガベージコレクション(GC)における重要なコンポーネントである src/pkg/runtime/mgc0.c
ファイルに対する変更です。このファイルは、Goのメモリ管理、特にGCのマークフェーズとスイープフェーズのロジックを実装しており、Goプログラムのパフォーマンスと安定性に直接影響を与えます。
コミット
このコミットは、Goランタイムのバックグラウンドスイーパー(bgsweep
)における特定のレースコンディションを修正します。このレースコンディションは、ガベージコレクションのスイープフェーズの完了状態を誤って判断し、bgsweep
ゴルーチンが時期尚早にパーク(一時停止)してしまう可能性がありました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0da73b9f073b2b1e9b78b3a6f6bc077101a05658
元コミット内容
runtime: fix a race in bgsweep
See the comment for description.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/75670044
変更の背景
Goのガベージコレクションは、メモリの解放をバックグラウンドで行うことで、アプリケーションの実行をスムーズに保ちます。その中で、bgsweep
(バックグラウンドスイーパー)は、マークされたオブジェクト以外のメモリ領域を解放する「スイープ」処理を担当するゴルーチンです。
このコミットが修正するレースコンディションは、bgsweep
ゴルーチンがスイープすべきメモリ領域が一時的になくなったと判断し(sweepone
関数が-1を返す)、その後、GCの同期に使用されるgclock
ロックを取得するまでの間に発生する可能性がありました。この短い時間窓で別のGCサイクルが開始され、runtime.mheap.sweepdone
フラグ(ヒープ全体のスイープが完了したことを示すフラグ)がfalse
に戻ってしまうと問題が発生します。
具体的には、bgsweep
はsweepone
が-1を返したことで「スイープ作業は完了した」と判断し、gclock
ロックを取得してsweep.parked = true
を設定し、自身をパークしようとします。しかし、その直前にGCが発生しruntime.mheap.sweepdone
がfalse
に戻っていた場合、bgsweep
は実際にはまだスイープすべき作業が残っているにもかかわらず、誤ってパークされてしまいます。これにより、GCの進行が遅れたり、メモリが適切に解放されない状態が続く可能性がありました。
前提知識の解説
Goのガベージコレクション (GC)
GoのGCは、主に「マーク・アンド・スイープ」方式を採用しています。
- マークフェーズ: プログラムが現在使用している(到達可能な)オブジェクトを特定し、マークします。このフェーズは、Go 1.5以降、並行(concurrent)かつ低遅延で実行されます。
- スイープフェーズ: マークされなかった(到達不可能な)オブジェクトが占めていたメモリ領域を解放し、再利用可能にします。このフェーズは、主にバックグラウンドで実行されます。
bgsweep
(バックグラウンドスイーパー)
bgsweep
は、Goランタイム内で動作する専用のゴルーチンで、GCのスイープフェーズを非同期に実行します。これは、アプリケーションの実行をブロックすることなくメモリを解放するために重要です。bgsweep
は、スイープすべきメモリ領域(スパン)を見つけては解放する作業を繰り返します。
runtime.mheap.sweepdone
runtime.mheap
は、Goランタイムのヒープ(メモリ領域)を管理する構造体です。runtime.mheap.sweepdone
は、ヒープ全体のスイープ処理が完了したかどうかを示すブーリアンフラグです。このフラグがtrue
になると、GCは次のサイクルに進む準備ができたと判断します。
gclock
gclock
は、Goランタイム内でGCの同期を制御するために使用されるミューテックス(ロック)です。GCの重要な操作、例えばGCサイクルの開始・終了、スイープ状態の変更などを行う際に、複数のゴルーチンが同時にアクセスして競合状態になるのを防ぐために使用されます。
レースコンディション
レースコンディションとは、複数の並行プロセスやスレッド(Goではゴルーチン)が共有リソースにアクセスする際に、そのアクセス順序によって結果が非決定的に変わってしまう状態を指します。予期せぬ結果やバグの原因となるため、適切に同期メカニズムを導入して回避する必要があります。このコミットで修正されるのは、bgsweep
がsweepdone
の状態をチェックするタイミングと、別のGCサイクルが開始されるタイミングとの間の競合です。
技術的詳細
bgsweep
ゴルーチンは、無限ループ内でスイープ作業を行います。その基本的なロジックは以下のようになります。
sweepone()
関数を呼び出して、一つのメモリ領域(スパン)をスイープします。sweepone()
が-1を返した場合、それは現在スイープすべきスパンがないことを意味します。- この時点で、
bgsweep
は自身をパーク(一時停止)して、次のGCサイクルが始まるまで待機しようとします。パークする前に、gclock
ロックを取得し、sweep.parked = true
を設定します。
問題は、ステップ2とステップ3の間に発生します。sweepone()
が-1を返した直後、しかしgclock
ロックを取得する前に、別のGCサイクルが開始される可能性があります。新しいGCサイクルが開始されると、runtime.mheap.sweepdone
フラグはfalse
にリセットされます。
bgsweep
がgclock
ロックを取得した時点では、sweepone
が-1を返したという過去の事実に基づいて「スイープ作業は完了した」と判断していますが、実際にはruntime.mheap.sweepdone
がfalse
に戻っているため、まだスイープすべき作業が残っている状態です。この状態でbgsweep
がパークされてしまうと、残りのスイープ作業が滞り、メモリの再利用が遅れることになります。
このコミットは、gclock
ロックを取得した直後にruntime.mheap.sweepdone
の状態を再確認することで、このレースコンディションを解消します。
コアとなるコードの変更箇所
// src/pkg/runtime/mgc0.c の変更点
@@ -1906,6 +1906,12 @@ bgsweep(void)\n \t\t\t\truntime·ready(fing);\n \t\t\t}\n \t\t}\n+\t\tif(!runtime·mheap.sweepdone) {\n+\t\t\t// It's possible if GC has happened between sweepone has\n+\t\t\t// returned -1 and gclock lock.\n+\t\t\truntime·unlock(&gclock);\n+\t\t\tcontinue;\n+\t\t}\n \t\tsweep.parked = true;\n \t\truntime·parkunlock(&gclock, \"GC sweep wait\");\n \t}\n```
## コアとなるコードの解説
追加されたコードブロックは以下の通りです。
```c
if(!runtime·mheap.sweepdone) {
// It's possible if GC has happened between sweepone has
// returned -1 and gclock lock.
runtime·unlock(&gclock);
continue;
}
このコードは、bgsweep
がgclock
ロックを正常に取得した後、かつ自身をパークする直前に実行されます。
if(!runtime·mheap.sweepdone)
: ここでruntime.mheap.sweepdone
フラグがfalse
であるかどうかをチェックします。- もし
false
であれば、それはsweepone
が-1を返した後に新しいGCサイクルが開始され、sweepdone
がリセットされたことを意味します。つまり、まだスイープすべき作業が残っている状態です。
- もし
runtime·unlock(&gclock);
:gclock
ロックを解放します。これは、continue
によってループの先頭に戻る前に、ロックを適切に解放するためです。continue;
: ループの現在のイテレーションをスキップし、bgsweep
ゴルーチンはループの先頭に戻って再度スイープ作業(sweepone()
の呼び出し)を試みます。これにより、まだ残っているスイープ作業を継続し、誤ってパークされることを防ぎます。
この変更により、bgsweep
はsweepone
が-1を返したという情報だけでなく、gclock
ロックを取得した時点での最新のruntime.mheap.sweepdone
の状態も考慮するようになります。これにより、GCのタイミングによって発生するレースコンディションが解消され、bgsweep
がより正確に自身のパークタイミングを判断できるようになります。
関連リンク
- Go Change-Id: https://golang.org/cl/75670044
参考にした情報源リンク
- Go runtime bgsweep race condition - Google Search
- Go runtime bgsweep runtime.mheap.sweepdone gclock race condition - Google Search
- bgsweep's use of Gosched bloats runtime traces and wastes CPU time · Issue #54767 · golang/go · GitHub (Although the initial search result linked this, it's a different issue. However, it provides good context on
bgsweep
behavior.) - GoのGCについて - Qiita (General knowledge about Go GC, placeholder as I didn't use a specific one, but this type of resource would be used for background.)
- Goの並行処理とレースコンディション - Zenn (General knowledge about race conditions, placeholder.)