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

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

このコミットは、Goランタイムからデバッグ用のプリエンプション無効化機能を削除し、同時にGo 1.2のドキュメントにゴルーチンの関数エントリーでのプリエンプションに関する記述を追加するものです。これにより、ランタイムのコードベースがクリーンアップされ、プリエンプションの挙動がより明確になります。

コミット

commit 10ebb84d4801beb9fb86e6e6d156229e9dad0883e3
Author: Russ Cox <rsc@golang.org>
Date:   Mon Aug 5 16:06:24 2013 -0400

    runtime: remove debugging knob to turn off preemption
    
    It's still easy to turn off, but the builders are happy.
    Also document.
    
    R=golang-dev, iant, dvyukov
    CC=golang-dev
    https://golang.org/cl/12371043

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

https://github.com/golang/go/commit/10ebb84d4801beb9fb86e6d156229e9dad0883e3

元コミット内容

ランタイムからプリエンプションを無効にするためのデバッグ用ノブを削除しました。 この機能は依然として簡単に無効化できますが、ビルドシステムがより安定します。 また、ドキュメントも更新しました。

変更の背景

Goランタイムにおけるゴルーチンのプリエンプション(横取り)は、スケジューラの公平性を保ち、特定のゴルーチンがCPUを独占するのを防ぐために非常に重要なメカニズムです。しかし、開発やデバッグの過程で、プリエンプションの挙動を一時的に制御したい場合があります。

このコミット以前は、src/pkg/runtime/proc.c内にプリエンプションを無効化するためのデバッグ用のコード(if(0) return;)が存在していました。これは、プリエンプションが導入された初期段階で、その挙動を検証したり、問題が発生した場合に切り分けを行ったりするために設けられたものと考えられます。

しかし、このようなデバッグ用の「ノブ(つまみ)」がコードベースに残っていると、以下のような問題が生じる可能性があります。

  1. ビルドの安定性: デバッグ用のコードが残っていると、コンパイラの警告や最適化の妨げになる可能性があり、ビルドシステムの安定性に影響を与えることがあります。コミットメッセージにある「builders are happy」という記述は、このデバッグコードがビルドプロセスに何らかの不満を与えていたことを示唆しています。
  2. コードの複雑性: 不要なデバッグコードは、コードベースの可読性を低下させ、メンテナンスを困難にします。
  3. 意図しない挙動: 誤ってデバッグ用のノブが有効化されたままリリースされると、予期せぬパフォーマンス問題やデッドロックを引き起こす可能性があります。

このコミットは、これらの問題を解決するために、もはや不要となったデバッグ用のプリエンプション無効化コードを削除し、同時にGo 1.2のリリースノートにプリエンプションの重要な変更点(関数エントリーでのプリエンプション)を明記することで、ランタイムの挙動をより明確にすることを目的としています。

前提知識の解説

ゴルーチン (Goroutine)

Go言語におけるゴルーチンは、軽量な並行実行単位です。OSのスレッドよりもはるかに軽量であり、数百万個のゴルーチンを同時に実行することも可能です。ゴルーチンはGoランタイムによって管理され、GoスケジューラによってOSスレッドにマッピングされて実行されます。

Goスケジューラ (Go Scheduler)

Goスケジューラは、ゴルーチンをOSスレッドに効率的に割り当て、実行を管理するGoランタイムの重要なコンポーネントです。スケジューラは、M (Machine/OSスレッド)、P (Processor/論理プロセッサ)、G (Goroutine) という3つの主要な要素から構成されるM-P-Gモデルを採用しています。

  • G (Goroutine): 実行されるコードの単位。
  • M (Machine): OSスレッド。ゴルーチンを実行する実際のワーカー。
  • P (Processor): 論理プロセッサ。MがGを実行するために必要なコンテキスト(ローカルキュー、スケジューラ状態など)を提供します。Pの数は通常、CPUコアの数に等しいです。

スケジューラは、Pに割り当てられたMがGを実行し、Gがブロックされたり、プリエンプションされたりすると、別のGをPに割り当てて実行を継続します。

プリエンプション (Preemption)

プリエンプションとは、実行中のタスク(この場合はゴルーチン)が、そのタスク自身の意思とは関係なく、スケジューラによって強制的に実行を中断され、別のタスクにCPUが割り当てられるメカニズムです。これにより、特定のタスクがCPUを独占するのを防ぎ、システム全体の応答性と公平性を保ちます。

Goにおけるプリエンプションには、主に以下の2種類があります。

  1. 協調的プリエンプション (Cooperative Preemption): ゴルーチンが明示的にスケジューラに制御を返す(例: runtime.Gosched()の呼び出し、チャネル操作、システムコールなど)ことで発生するプリエンプションです。これは、ゴルーチンが自ら「私は一時停止しても構いません」とスケジューラに伝える形です。
  2. 非協調的プリエンプション (Non-cooperative Preemption): ゴルーチンが自ら制御を返さない場合でも、スケジューラが強制的に実行を中断させるプリエンプションです。これは、無限ループなどでCPUを占有し続けるゴルーチンが存在する場合に、他のゴルーチンが飢餓状態になるのを防ぐために重要です。

このコミットが関連する「関数エントリーでのプリエンプション」は、非協調的プリエンプションの一種であり、Go 1.14で本格的に導入された非同期プリエンプションの基盤となる重要な概念です。

関数エントリーでのプリエンプション (Preemption at Function Entry)

Go 1.14以降、Goランタイムは「関数エントリーでのプリエンプション」というメカニズムを導入しました。これは、ゴルーチンが関数呼び出しを行う際に、その関数のプロローグ(関数の冒頭部分)に挿入されたチェックを利用してプリエンプションを行うものです。

具体的な仕組みは以下の通りです。

  • sysmon ゴルーチン: Goランタイムには、バックグラウンドで動作する sysmon (system monitor) ゴルーチンが存在します。これは定期的に実行され、長時間実行されているゴルーチンがないかを監視します。
  • スタックガード (Stack Guard): 各ゴルーチンには、スタックのオーバーフローを検出するための「スタックガード」と呼ばれる領域があります。
  • 関数プロローグのチェック: Goコンパイラは、すべての関数のプロローグにスタックガードのチェックを挿入します。通常、このチェックはスタックの拡張が必要かどうかを判断するために使用されます。
  • プリエンプションのトリガー: sysmon が長時間実行されているゴルーチンを検出すると、そのゴルーチンのスタックガードを「汚染(poison)」します。次に、そのゴルーチンが関数呼び出しを行うと、関数のプロローグにあるスタックガードのチェックが失敗します。
  • 制御の譲渡: このチェックの失敗が、プリエンプションロジックをトリガーします。実行中のゴルーチンはOSスレッド(M)から切り離され、グローバルな実行キューに移動されます。これにより、Mは別のゴルーチンを実行できるようになります。

このメカニズムにより、Goスケジューラは、runtime.Gosched()のような明示的なyieldポイントがないCPUバウンドなループを実行しているゴルーチンでもプリエンプションできるようになり、Goスケジューラの全体的な応答性と公平性が向上しました。

このコミットは、Go 1.2のドキュメントにこの「関数エントリーでのプリエンプション」が導入されたことを明記しており、当時のGoランタイム開発におけるプリエンプションの重要性と進化を示しています。

技術的詳細

このコミットの技術的詳細は、主に以下の2点に集約されます。

  1. デバッグ用プリエンプション無効化コードの削除: src/pkg/runtime/proc.c ファイル内の 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(0) return;
    

    このコードは、if(0) という条件によって常に実行されないようになっていましたが、コンパイラが「到達不能なコード」として警告を発するのを抑制するために if(1) のコメントが付けられていました。これは、プリエンプション機能がまだ開発途上にあり、トレースバックルーチン(デバッグ時にスタックトレースを生成する機能)との連携が不十分だった時期に、一時的にプリエンプションを無効化するための「デバッグ用ノブ」として機能していたと考えられます。

    if(0) return; という記述は、プリエンプションロジックの残りの部分が実行されないようにするためのもので、実質的に preemptone 関数が何もしないようにしていました。このコードの削除は、プリエンプション機能が十分に安定し、デバッグ目的で一時的に無効化する必要がなくなったことを示しています。

  2. Go 1.2 ドキュメントへの追記: doc/go1.2.txt ファイルに、Go 1.2の変更点として以下の行が追加されました。

    +runtime: preemption of goroutines at function entry (CL 12371043).
    

    これは、Go 1.2でゴルーチンの「関数エントリーでのプリエンプション」が導入されたことを公式にドキュメント化したものです。この記述は、Goランタイムがより洗練されたプリエンプション戦略を採用し、CPUバウンドなゴルーチンに対しても公平なスケジューリングを保証する方向へ進化していることを示しています。

    CL 12371043 は、このプリエンプション機能が導入された元の変更リスト(Change List)への参照であり、詳細な技術的背景や実装を知るための手がかりとなります。

これらの変更は、Goランタイムがデバッグ段階から安定した運用段階へと移行し、より堅牢で効率的なゴルーチン管理メカニズムを確立していく過程の一部を示しています。デバッグ用のコードを削除することで、コードベースの健全性が保たれ、ドキュメントを更新することで、ユーザーや開発者に対してランタイムの重要な変更が明確に伝えられます。

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

doc/go1.2.txt

--- a/doc/go1.2.txt
+++ b/doc/go1.2.txt
@@ -26,6 +26,7 @@ image/draw: added Quantizer type (CL 11148043).
 image/gif: added Encode and EncodeAll (CL 10896043).
 io: Copy prioritizes WriterTo over ReaderFrom (CL 9462044).
 net: new build tag netgo for building a pure Go net package (CL 7100050).
+runtime: preemption of goroutines at function entry (CL 12371043).
 sort: new Stable function provides stable sort (CL 9612044).
 syscall: implemented Sendfile for Darwin, added Syscall9 for Darwin/amd64 (CL 10980043).
 testing: AllocsPerRun is now quantized to an integer (the type is still float64) (CL 9837049).

src/pkg/runtime/proc.c

--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -2275,12 +2275,6 @@ 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(0) return;
-
 	mp = p->m;
 	if(mp == nil || mp == m)
 		return;

コアとなるコードの解説

doc/go1.2.txt の変更

このファイルはGo 1.2のリリースノートまたは変更履歴をまとめたドキュメントです。追加された行 +runtime: preemption of goroutines at function entry (CL 12371043). は、Go 1.2のランタイムにおける重要な新機能として、「ゴルーチンの関数エントリーでのプリエンプション」が導入されたことを明記しています。これは、Goランタイムがより高度なスケジューリング能力を獲得し、CPUバウンドなゴルーチンに対しても公平な実行を保証するようになったことを公式にアナウウンスするものです。CL 12371043 は、この機能の実装に関する具体的な変更リストへの参照です。

src/pkg/runtime/proc.c の変更

src/pkg/runtime/proc.c はGoランタイムのプロセッサ(P)とゴルーチン(G)の管理、スケジューリングに関連するC言語のコードが含まれています。

削除されたコードブロックは preemptone 関数内にありました。preemptone 関数は、特定のP(論理プロセッサ)に割り当てられたM(OSスレッド)上で実行されているゴルーチンをプリエンプション(横取り)しようとするロジックの一部です。

削除されたコードは以下の通りです。

// 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(0) return;
  • // 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(0) return; というコードは、常に return を実行するため、その後の preemptone 関数の残りのコードは決して実行されません。コンパイラはこのような「到達不能なコード」に対して警告を発することがあります。このコメントは、その警告を抑制するために、あたかも if(1) のように常に真であるかのように見せかける意図があったことを説明しています。しかし、実際には if(0) なので、このブロックは常にスキップされ、プリエンプションは無効化されていました。

このコードブロックが削除されたということは、以下のことを意味します。

  1. プリエンプション機能の安定化: プリエンプション機能が十分に安定し、トレースバックルーチンとの連携も問題なくなったため、一時的な無効化が不要になった。
  2. デバッグ用コードのクリーンアップ: 開発段階で一時的に導入されたデバッグ用の「ノブ」が、もはや必要なくなり、コードベースから削除された。これにより、コードの可読性とメンテナンス性が向上し、ビルドシステムにとっても好ましい状態になった(「builders are happy」)。

この変更は、Goランタイムが成熟し、プリエンプションがGoの並行処理モデルの不可欠な部分として確立されていく過程を示しています。

関連リンク

参考にした情報源リンク