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

[インデックス 16686] ファイルの概要

このコミットは、Goランタイムにおけるプリエンプション(preemption)機能を一時的に無効化するものです。具体的には、src/pkg/runtime/proc.cファイル内のpreemptone関数に、プリエンプションを即座に終了させるためのコードが追加されています。

コミット

commit 08e064135dfd6ec5cd09ac07134a9817425aac06
Author: Russ Cox <rsc@golang.org>
Date:   Mon Jul 1 17:57:09 2013 -0400

    runtime: disable preemption
    
    There are various problems, and both Dmitriy and I
    will be away for the next week. Make the runtime a bit
    more stable while we're gone.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/10848043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/08e064135dfd6ec5cd09ac07134a9817425aac06

元コミット内容

runtime: disable preemption

There are various problems, and both Dmitriy and I
will be away for the next week. Make the runtime a bit
more stable while we're gone.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/10848043

変更の背景

このコミットは、Goランタイムのプリエンプション機能に「様々な問題」が存在していたために行われました。コミットメッセージによると、コミットの著者であるRuss Cox氏とDmitriy氏が翌週不在になるため、その間にランタイムの安定性を確保することを目的として、一時的にプリエンプションを無効化する措置が取られました。

Goのランタイムにおけるプリエンプションは、長時間実行されるゴルーチンがCPUを占有し続けることを防ぎ、他のゴルーチンにも公平に実行機会を与えるための重要なメカニズムです。しかし、この機能の実装は複雑であり、特にスタックのトレースバックやガベージコレクションとの連携において、デバッグが困難な問題を引き起こすことがあります。

この時期(2013年7月頃)のGoランタイムは、プリエンプションのメカニズムがまだ成熟しておらず、安定性に課題を抱えていたと考えられます。開発者が長期休暇に入る前に、既知の不安定要素を一時的に排除することで、予期せぬクラッシュやデッドロックなどの深刻な問題が発生するリスクを低減しようとした、緊急的な対応と言えるでしょう。

前提知識の解説

Goランタイムとゴルーチン

Go言語は、軽量な並行処理の単位である「ゴルーチン(goroutine)」を特徴としています。ゴルーチンはOSのスレッドよりもはるかに軽量であり、数百万個のゴルーチンを同時に実行することも可能です。Goランタイムは、これらのゴルーチンをOSスレッドにマッピングし、スケジューリングを行う役割を担っています。

Goスケジューラ (M:Nスケジューリング)

Goランタイムには、独自のスケジューラが組み込まれています。これは「M:Nスケジューリング」と呼ばれるモデルを採用しており、M個のゴルーチンをN個のOSスレッドに多重化して実行します。このスケジューラは、ゴルーチンの作成、実行、ブロック、そしてプリエンプションを管理します。

プリエンプション(Preemption)

プリエンプションとは、実行中のタスク(この場合はゴルーチン)が自発的にCPUを解放することなく、スケジューラによって強制的に実行を中断させられるメカニズムのことです。Goランタイムにおけるプリエンプションの主な目的は以下の通りです。

  1. 公平性(Fairness): 長時間CPUを占有するゴルーチン(例: 無限ループや計算量の多い処理)が存在する場合、他のゴルーチンが全く実行されない「飢餓状態(starvation)」に陥る可能性があります。プリエンプションは、このようなゴルーチンを強制的に中断させ、他のゴルーチンに実行機会を与えることで、公平なリソース配分を実現します。
  2. レイテンシの改善: 応答性の高いアプリケーションでは、特定の処理が長時間ブロックされることを避けたい場合があります。プリエンプションにより、重要な処理が迅速に実行される機会が増え、全体的なレイテンシが改善されます。
  3. ガベージコレクション(GC)の効率化: Goのガベージコレクタは、一部のフェーズで全てのゴルーチンを停止させる必要があります(Stop-the-World)。プリエンプションは、GCがゴルーチンを安全に停止させ、GC処理を効率的に進めるために重要な役割を果たします。

Goにおけるプリエンプションは、主に以下の2つの方法で行われます。

  • 協調的プリエンプション(Cooperative Preemption): ゴルーチンが関数呼び出しやチャネル操作など、特定の安全なポイントで自発的にスケジューラに制御を返すことで行われます。初期のGoでは、これが主なプリエンプションの手段でした。
  • 非協調的プリエンプション(Asynchronous Preemption): ゴルーチンが自発的に制御を返さない場合でも、タイマーやシグナルなどのOSレベルのメカニズムを利用して、スケジューラが強制的にゴルーチンを中断させる方法です。これは、無限ループなどで協調的プリエンプションが機能しない場合に特に重要です。このコミットが行われた時期は、非協調的プリエンプションの実装がまだ発展途上であり、安定性に課題があったと考えられます。

スタックトレースバック

Goランタイムは、ゴルーチンの実行中にスタック情報を正確にトレースバックできる必要があります。これは、パニック発生時のデバッグ情報出力、プロファイリング、そして特にプリエンプションにおいて、ゴルーチンの状態を保存・復元するために不可欠です。プリエンプションは、ゴルーチンの実行を任意の時点で中断させるため、その時点でのスタックの状態を正確に把握し、後で安全に再開できるような堅牢なトレースバックルーチンが求められます。コミットメッセージにある「Preemption requires more robust traceback routines.」というコメントは、この課題を明確に示しています。

技術的詳細

このコミットは、Goランタイムのプリエンプション機能を一時的に無効化するために、src/pkg/runtime/proc.cファイル内のpreemptone関数に直接的な変更を加えています。

preemptone関数は、特定のプロセッサ(P)上で実行されているゴルーチンをプリエンプト(強制的に中断)しようとするGoランタイムの内部関数です。通常、この関数は、長時間実行されているゴルーチンを検出し、その実行を中断して他のゴルーチンにCPUを明け渡すためのロジックを含んでいます。

しかし、このコミットでは、preemptone関数の冒頭に以下のコードが追加されています。

// Preemption requires more robust traceback routines.
// For now, disable.
// The if(1) silences a compiler warning about the rest of the
// function being unreachable.
if(1) return;

この変更の技術的な意味は以下の通りです。

  1. if(1) return;: これはC言語のコードであり、常に真となる条件式if(1)によって、その直後のreturn;文が常に実行されることを意味します。これにより、preemptone関数が呼び出されると、その関数の本来のロジック(ゴルーチンのプリエンプション処理)が実行されることなく、即座に関数から抜けてしまいます。結果として、Goランタイムはゴルーチンをプリエンプトしようとしても、実際には何も行われない状態になります。
  2. プリエンプションの無効化: 上記のif(1) return;によって、Goランタイムのプリエンプションメカニズムが事実上無効化されます。これにより、ゴルーチンは自発的にスケジューラに制御を返さない限り、CPUを占有し続けることが可能になります。
  3. コンパイラ警告の抑制: コメントにある「The if(1) silences a compiler warning about the rest of the function being unreachable.」は、この変更がCコンパイラによって「到達不能なコード(unreachable code)」に関する警告を発生させる可能性があることを示唆しています。if(1) return;の後に続くコードは決して実行されないため、コンパイラはこれを警告する場合があります。if(1)という形式は、この警告を抑制するための一般的なC言語のイディオムです。
  4. 「より堅牢なトレースバックルーチン」の必要性: コメント「Preemption requires more robust traceback routines.」は、プリエンプションが正しく機能するためには、ゴルーチンのスタックを正確にトレースバックし、その状態を安全に保存・復元できる、より堅牢なルーチンが必要であることを示しています。この時点では、そのルーチンが十分に成熟していなかったため、プリエンプションを一時的に無効化するという選択が取られました。

この変更は、Goランタイムの安定性を一時的に優先するための、非常に直接的かつ影響の大きいハックと言えます。プリエンプションが無効化されることで、特定のゴルーチンがCPUを独占し、他のゴルーチンが実行されない「飢餓状態」に陥る可能性が高まります。しかし、開発者が不在の間に、プリエンプションに関連する未知のバグによるクラッシュを防ぐという目的のためには、有効な手段でした。

コアとなるコードの変更箇所

変更はsrc/pkg/runtime/proc.cファイルに集中しています。

--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -2149,6 +2149,12 @@ preemptone(P *p)
 	M *mp;
 	G *gp;
 
+// Preemption requires more robust traceback routines.
+// For now, disable.
+// The if(1) silences a compiler warning about the rest of the
+// function being unreachable.
+if(1) return;
+
 	mp = p->m;
 	if(mp == nil || mp == m)
 		return;

具体的には、preemptone(P *p)関数の冒頭、既存の変数宣言の直後に、以下の6行が追加されています。

  1. // Preemption requires more robust traceback routines.
  2. // For now, disable.
  3. // The if(1) silences a compiler warning about the rest of the
  4. // function being unreachable.
  5. if(1) return;
  6. (空行)

コアとなるコードの解説

追加されたコードは、preemptone関数の実行フローを強制的に中断させるものです。

preemptone関数は、Goランタイムのスケジューラが、特定のプロセッサ(P)に割り当てられたゴルーチンをプリエンプトする必要があると判断した際に呼び出されます。この関数が呼び出されると、通常は以下の処理が行われます(このコミットで無効化される部分):

  1. 対象のプロセッサpに現在関連付けられているM(OSスレッド)を取得します。
  2. Mがnilでないか、または現在のMと同じでないかを確認します。
  3. 対象のゴルーチンgpを取得し、その状態を確認します。
  4. ゴルーチンがプリエンプト可能な状態であれば、その実行を中断し、スケジューラに制御を戻すための処理(例: スタックの保存、レジスタの保存など)を実行します。

しかし、このコミットで追加されたif(1) return;という行により、preemptone関数が呼び出されると、上記の本来のプリエンプションロジックが一切実行されることなく、関数が即座に終了してしまいます。

これにより、Goランタイムはゴルーチンを強制的に中断させる能力を失います。結果として、Goプログラムは協調的プリエンプション(関数呼び出しやチャネル操作など、ゴルーチンが自発的にスケジューラに制御を返すポイント)にのみ依存するようになります。無限ループのようなCPUバウンドなゴルーチンは、他のゴルーチンに実行機会を与えることなく、長時間CPUを占有し続ける可能性があります。

この変更は、プリエンプション機能がまだ不安定であり、特にスタックトレースバックの堅牢性が不足しているという認識に基づいています。開発者が不在の間に、この不安定な機能を一時的に無効化することで、ランタイム全体のクラッシュや予期せぬ動作を防ぐための、短期的な安定化策として導入されました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントやブログ(特にGoスケジューラやプリエンプションに関する記事)
  • Goランタイムのソースコード(src/pkg/runtime/proc.cの関連部分)
  • GoのIssueトラッカーやメーリングリストの議論(プリエンプションの安定性に関する過去の議論)
  • C言語におけるif(1) return;のようなイディオムに関する一般的なプログラミング情報
  • Goのプリエンプションに関する技術記事や解説