[インデックス 15720] ファイルの概要
このコミットは、Goランタイムのスケジューラにネットワークポーリングのサポートを追加するものです。これは、ネットワークポーラーをランタイムに統合するという、より大きな変更の一部として行われました。具体的には、GoのスケジューラがネットワークI/Oの準備ができたゴルーチンを効率的に検出・実行できるようにするための変更が含まれています。
コミット
commit c21188473103a14beb3b0950fb2331fea7e16d80
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Tue Mar 12 21:14:26 2013 +0400
runtime: add network polling support into scheduler
This is a part of the bigger change that moves network poller into runtime:
https://golang.org/cl/7326051/
R=golang-dev, bradfitz, mikioh.mikioh, rsc
CC=golang-dev
https://golang.org/cl/7448048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c21188473103a14beb3b0950fb2331fea7e16d80
元コミット内容
runtime: add network polling support into scheduler
This is a part of the bigger change that moves network poller into runtime:
https://golang.org/cl/7326051/
R=golang-dev, bradfitz, mikioh.mikioh, rsc
CC=golang-dev
https://golang.org/cl/7448048
変更の背景
このコミットは、Goランタイムにおけるネットワークポーラーのアーキテクチャ変更の一環として導入されました。以前は、ネットワークI/Oの監視はGoランタイムとは独立した形で処理されていた可能性があります。しかし、Goの並行処理モデル(ゴルーチンとスケジューラ)とのより密接な統合を図るため、ネットワークポーラーの機能をランタイム内部に移動させるという大きな計画がありました。
この変更の主な目的は、ネットワークI/Oが準備できた際に、Goスケジューラがより効率的かつ迅速に該当するゴルーチンを再開できるようにすることです。これにより、ネットワークアプリケーションのパフォーマンス向上、特に多数の同時接続を扱う際のレイテンシとスループットの改善が期待されます。コミットメッセージに記載されている https://golang.org/cl/7326051/
が、この「より大きな変更」の主要なチェンジリスト(CL)を示しています。
前提知識の解説
このコミットを理解するためには、Goランタイムの以下の主要な概念を理解しておく必要があります。
- ゴルーチン (Goroutine): Goにおける軽量な実行スレッドです。数百万のゴルーチンを同時に実行できる設計になっています。
- Goスケジューラ: ゴルーチンをOSスレッド(M)にマッピングし、実行を管理するGoランタイムのコンポーネントです。Goスケジューラは、M:Nスケジューリングモデルを採用しており、M個のOSスレッド上でN個のゴルーチンを実行します。
- P (Processor): 論理プロセッサを表します。ゴルーチンを実行するためのコンテキストを提供し、Mとゴルーチンの間の仲介役となります。各Pはローカルな実行キューを持ち、ゴルーチンを保持します。
- M (Machine/OS Thread): OSスレッドを表します。Pに割り当てられ、そのPのローカルキューからゴルーチンを取得して実行します。
- G (Goroutine): ゴルーチン自体を表すデータ構造です。
- ネットワークポーラー (Network Poller): ネットワークI/O操作(ソケットの読み書きなど)がブロックせずに実行可能になったことを検出するメカニズムです。Linuxのepoll、macOS/FreeBSDのkqueue、WindowsのIOCPなどがこれに該当します。Goランタイムは、これらのOS固有の機能を利用して、多数のネットワーク接続を効率的に監視します。
netpoll
: Goランタイムが提供するネットワークポーリングの抽象化されたインターフェースです。この関数は、ネットワークI/Oの準備ができたゴルーチンを返します。block
引数によって、ブロックするか非ブロックでポーリングを行うかを制御できます。proc.c
: Goランタイムのスケジューリングロジックの大部分が含まれるC言語のソースファイルです。ゴルーチンの作成、実行、ブロック、再開などの処理が定義されています。sysmon
(System Monitor): Goランタイムのバックグラウンドで動作するM(OSスレッド)です。定期的に実行され、ガベージコレクションのトリガー、プリエンプションの実行、ネットワークポーラーの呼び出しなど、様々なハウスキーピングタスクを実行します。findrunnable
: スケジューラが次に実行すべきゴルーチンを見つけるための関数です。ローカルキュー、グローバルキュー、他のPからのゴルーチン窃盗(work stealing)などを試みます。
技術的詳細
このコミットの技術的な核心は、GoスケジューラがネットワークI/Oの準備ができたゴルーチンを、より積極的にかつ効率的にスケジューリングプロセスに組み込む点にあります。
-
netpoll_stub.c
の追加:- このファイルは、Windowsプラットフォーム向けに
runtime·netpoll
関数のスタブ実装を提供します。これは、Windowsが統合されたネットワークポーラーをサポートしていない(または当時のGoランタイムがまだそのサポートを完全に統合していなかった)ためです。 +build windows
ディレクティブにより、このファイルはWindowsビルドでのみコンパイルされます。runtime·netpoll
はnil
を返し、ネットワークポーリングが利用できないことを示します。
- このファイルは、Windowsプラットフォーム向けに
-
proc.c
の変更:Sched
構造体へのlastpoll
フィールドの追加:struct Sched { // ... uint64 lastpoll; };
lastpoll
は、最後にネットワークポーリングが実行された時刻(ナノ秒単位)を記録するために使用されます。これにより、ポーリングの頻度を制御し、過度なポーリングを防ぎます。runtime·schedinit
でのlastpoll
の初期化:
スケジューラの初期化時に、runtime·sched.lastpoll = runtime·nanotime();
lastpoll
が現在の時刻で初期化されます。runtime·starttheworld
での非ブロックポーリング:
システムが起動する際に、非ブロックで一度ネットワークポーリングを実行し、準備ができたゴルーチンがあればすぐにスケジューラに注入します。gp = runtime·netpoll(false); // non-blocking injectglist(gp);
findrunnable
でのネットワークポーリングの統合:findrunnable
関数は、実行可能なゴルーチンを見つけるための主要なループです。このコミットにより、ゴルーチンを見つけるための試行順序にネットワークポーリングが組み込まれました。- 非ブロックポーリング:
gp = runtime·netpoll(false); // non-blocking if(gp) { injectglist(gp->schedlink); gp->status = Grunnable; return gp; }
findrunnable
が他のPやグローバルキューからゴルーチンを見つけられなかった場合、まず非ブロックでネットワークポーリングを試みます。これにより、ネットワークI/Oが準備できたゴルーチンがあれば、すぐに実行キューに戻されます。 - ブロックポーリング:
もしスケジューラが実行可能なゴルーチンを全く見つけられず、かつスピンしているMの数がPの数以上である場合(つまり、CPUを無駄に消費している可能性がある場合)、スケジューラはブロックするネットワークポーリングを試みます。これは、新しいネットワークイベントが発生するまでMをスリープさせることで、CPUリソースの無駄な消費を防ぎます。if(runtime·xchg64(&runtime·sched.lastpoll, 0) != 0) { // ... gp = runtime·netpoll(true); // block until new work is available runtime·atomicstore64(&runtime·sched.lastpoll, runtime·nanotime()); // ... }
lastpoll
を0に設定し、ポーリング後に現在の時刻で更新することで、ポーリングが実行中であることを示し、他のMが同時にブロックポーリングを行うのを防ぎます。
- 非ブロックポーリング:
injectglist
関数の追加:
この新しい関数は、static void injectglist(G *glist) { // ... runtime·lock(&runtime·sched); for(n = 0; glist; n++) { gp = glist; glist = gp->schedlink; gp->status = Grunnable; globrunqput(gp); // Add to global runnable queue } runtime·unlock(&runtime·sched); for(; n && runtime·sched.npidle; n--) startm(nil, false); // Start new M if idle P available }
netpoll
から返された実行可能なゴルーチンのリストをスケジューラに注入するために使用されます。ゴルーチンはGrunnable
ステータスに設定され、グローバル実行キューに追加されます。また、注入されたゴルーチンの数に応じて、アイドル状態のPがあれば新しいMを起動し、ゴルーチンをすぐに実行できるようにします。これはGCと並行して実行できるように設計されています。sysmon
でのネットワークポーリング:// poll network if not polled for more than 10ms lastpoll = runtime·atomicload64(&runtime·sched.lastpoll); now = runtime·nanotime(); if(lastpoll != 0 && lastpoll + 10*1000*1000 > now) { gp = runtime·netpoll(false); // non-blocking injectglist(gp); }
sysmon
スレッドも定期的に(10ミリ秒ごとに)非ブロックでネットワークポーリングを実行し、準備ができたゴルーチンをスケジューラに注入します。これにより、ネットワークイベントが長時間処理されないことを防ぎます。
-
runtime.h
の変更:runtime·netpoll
関数のプロトタイプ宣言が追加されました。G* runtime·netpoll(bool);
これらの変更により、GoスケジューラはネットワークI/Oの準備ができたゴルーチンを、より迅速かつ効率的に検出し、実行キューに戻すことができるようになりました。これにより、ネットワークアプリケーションの応答性とスループットが向上します。
コアとなるコードの変更箇所
src/pkg/runtime/netpoll_stub.c
: 新規追加。Windows向けのruntime·netpoll
のスタブ実装。src/pkg/runtime/proc.c
:Sched
構造体にlastpoll
フィールドを追加。runtime·schedinit
でlastpoll
を初期化。runtime·starttheworld
で非ブロックnetpoll
を呼び出し、injectglist
でゴルーチンを注入。findrunnable
関数内で、非ブロックおよびブロックnetpoll
の呼び出しを追加し、ネットワークポーリングをスケジューリングロジックに統合。injectglist
関数を新規追加。sysmon
関数内で、定期的な非ブロックnetpoll
の呼び出しを追加。
src/pkg/runtime/runtime.h
:runtime·netpoll
関数のプロトタイプ宣言を追加。
コアとなるコードの解説
src/pkg/runtime/proc.c
の変更点
Sched
構造体への lastpoll
の追加
struct Sched {
// ...
uint64 lastpoll; // 最後にネットワークポーリングが実行された時刻 (ナノ秒)
};
このフィールドは、ネットワークポーリングの頻度を制御し、ポーリングが過剰に行われるのを防ぐために使用されます。
findrunnable
関数内の変更
findrunnable
は、スケジューラが次に実行すべきゴルーチンを見つけるための中心的な関数です。
static G*
findrunnable(void)
{
// ... (既存のゴルーチン探索ロジック)
// poll network (非ブロック)
gp = runtime·netpoll(false); // ネットワークI/Oが準備できたゴルーチンを非ブロックで取得
if(gp) {
injectglist(gp->schedlink); // 取得したゴルーチンをスケジューラに注入
gp->status = Grunnable; // 実行可能状態に設定
return gp; // 取得したゴルーチンを返す
}
// ... (スピンしているMの数が多い場合のブロックロジック)
// poll network (ブロック)
if(runtime·xchg64(&runtime·sched.lastpoll, 0) != 0) { // lastpollを0に設定し、ポーリング中であることを示す
// ... (エラーチェック)
gp = runtime·netpoll(true); // 新しいネットワークイベントが発生するまでブロック
runtime·atomicstore64(&runtime·sched.lastpoll, runtime·nanotime()); // ポーリング時刻を更新
if(gp) {
// ... (アイドルPがあれば取得し、ゴルーチンを注入)
injectglist(gp); // 取得したゴルーチンをスケジューラに注入
}
}
// ... (Mを停止するロジック)
}
この変更により、スケジューラはゴルーチンを探す際に、ネットワークI/Oの準備状況も考慮するようになりました。非ブロックポーリングは迅速な応答を可能にし、ブロックポーリングはCPUリソースの効率的な利用を促進します。
injectglist
関数の追加
// Injects the list of runnable G's into the scheduler.
// Can run concurrently with GC.
static void
injectglist(G *glist)
{
int32 n;
G *gp;
if(glist == nil)
return;
runtime·lock(&runtime·sched); // スケジューラロックを取得
for(n = 0; glist; n++) {
gp = glist;
glist = gp->schedlink;
gp->status = Grunnable; // ゴルーチンを実行可能状態に設定
globrunqput(gp); // グローバル実行キューに追加
}
runtime·unlock(&runtime·sched); // スケジューラロックを解放
for(; n && runtime·sched.npidle; n--)
startm(nil, false); // 注入されたゴルーチンの数に応じて、アイドルPがあれば新しいMを起動
}
この関数は、netpoll
から返されたゴルーチンをスケジューラに統合する役割を担います。これにより、ネットワークイベントによってブロックされていたゴルーチンが、速やかに実行可能な状態に戻されます。
sysmon
関数内の変更
static void
sysmon(void)
{
// ... (既存のsysmonロジック)
// poll network if not polled for more than 10ms
lastpoll = runtime·atomicload64(&runtime·sched.lastpoll);
now = runtime·nanotime();
if(lastpoll != 0 && lastpoll + 10*1000*1000 > now) { // 10ms以上ポーリングされていない場合
gp = runtime·netpoll(false); // 非ブロックでネットワークポーリング
injectglist(gp); // 準備ができたゴルーチンを注入
}
// ... (Pの再取得ロジック)
}
sysmon
はバックグラウンドで動作し、定期的にネットワークポーリングを行うことで、ネットワークイベントが長時間見過ごされることを防ぎます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/c21188473103a14beb3b0950fb2331fea7e16d80
- 関連するGo CL (Change List): https://golang.org/cl/7326051/
- 関連するGo CL (このコミット自体): https://golang.org/cl/7448048
参考にした情報源リンク
- https://github.com/golang/go/commit/c21188473103a14beb3b0950fb2331fea7e16d80
- https://golang.org/cl/7326051/
- https://golang.org/cl/7448048
- Go言語のランタイムスケジューラに関する一般的な知識 (Goのドキュメントやブログ記事など)