コンテンツにスキップ

スケジューラ

スケジューラはワークスティーリング設計を使用してプロセスを実行します。ワーカーはローカルdequeを維持し、アイドル時に互いからスティールします。

スケジューラはProcessインターフェースを実装する任意の型で動作:

type Process interface {
Init(ctx context.Context, method string, input payload.Payloads) error
Step(events []Event, out *StepOutput) error
Close()
}
メソッド目的
Initエントリメソッド名と入力引数でプロセスを準備
Step着信イベントで状態マシンを進め、出力にyieldを書き込み
Closeリソースを解放

Initmethodパラメータはどのエントリポイントを呼び出すかを指定。プロセスインスタンスは複数のエントリポイントを公開でき、呼び出し側がどれを実行するかを選択。これはスケジューラがプロセスを正しく開始していることの検証としても機能。

スケジューラはStep()を繰り返し呼び出し、イベント(yield完了、メッセージ)を渡してyield(ディスパッチするコマンド)を収集。プロセスはステータスとyieldをStepOutputバッファに書き込む。

type Event struct {
Type EventType // EventYieldCompleteまたはEventMessage
Tag uint64 // yield完了用の相関タグ
Data any // 結果データまたはメッセージペイロード
Error error // yieldが失敗した場合のエラー
}

スケジューラはデフォルトでGOMAXPROCS個のワーカーを生成。各ワーカーはキャッシュフレンドリーなLIFOアクセス用のローカルdequeを持つ。グローバルFIFOキューは新しいサブミッションとワーカー間転送を処理。プロセスはメッセージルーティング用にPIDで追跡。

flowchart TD
W[ワーカーがワークを必要] --> L{ローカルdeque?}
L -->|アイテムあり| LP[底からLIFOでポップ]
L -->|空| G{グローバルキュー?}
G -->|アイテムあり| GP[ポップ + 最大16個をバッチ転送]
G -->|空| S[ランダムな犠牲者からスティール]
S --> SH[犠牲者のdequeからStealHalfInto]

ワーカーは優先順位でソースをチェック:

優先度ソースパターン
1ローカルdequeLIFOポップ、ロックフリー、キャッシュフレンドリー
2グローバルキューバッチ転送付きFIFOポップ
3他のワーカー犠牲者のdequeから半分をスティール

グローバルからポップする際、ワーカーは1つのアイテムを取得し、最大16個をローカルdequeにバッチ転送。

各ワーカーはChase-Levワークスティーリングdequeを所有:

type Deque struct {
buffer atomic.Pointer[dequeBuffer]
top atomic.Int64 // 泥棒がここからスティール(CAS)
bottom atomic.Int64 // 所有者がここでプッシュ/ポップ
}

所有者は同期なしで底からプッシュ/ポップ(LIFO)。泥棒はCASを使用して上からスティール(FIFO)。これにより所有者は最近プッシュされたアイテムにキャッシュフレンドリーにアクセスでき、古いワークはスティーラーに分配。

StealHalfIntoは1回のCAS操作で半分のアイテムを取得し、競合を削減。

条件変数でブロックする前に、ワーカーは適応的にスピン:

スピン回数アクション
< 4タイトループ
4-15スレッドyield(runtime.Gosched
>= 16条件変数でブロック
stateDiagram-v2
[*] --> Ready: Submit
Ready --> Running: ワーカーによるCAS
Running --> Complete: 完了
Running --> Blocked: コマンドをyield
Running --> Idle: メッセージ待ち
Blocked --> Ready: CompleteYield
Idle --> Ready: Sendが到着
状態説明
Ready実行待ちキュー
RunningワーカーがStep()を実行中
Blockedyield完了待ち
Idleメッセージ待ち
Complete実行完了

ウェイクアップフラグは競合を処理:ハンドラがワーカーがまだプロセスを所有している(Running)間にCompleteYieldを呼び出すと、フラグを設定。ワーカーはディスパッチ後にフラグをチェックし、設定されていれば再キュー。

各プロセスはMPSC(multi-producer, single-consumer)イベントキューを持つ:

  • プロデューサー: コマンドハンドラ(CompleteYield)、メッセージ送信者(Send
  • コンシューマー: ワーカーがStep()でドレイン

スケジューラはrelay.Receiverを実装してメッセージをプロセスにルーティング。Send()が呼ばれると、byPIDマップでターゲットPIDを検索し、メッセージをイベントとしてプロセスキューにプッシュし、アイドルならプロセスをグローバルキューにプッシュしてウェイク。

シャットダウン時、スケジューラはすべての実行中プロセスにキャンセルイベントを送信し、完了またはタイムアウトを待機。ワーカーはワークがなくなると終了。