4 分で読了 #rust #並行と非同期
このページの目次

tokio ランタイム

tokio の reactor(epoll/kqueue/IOCP ベース) は非同期 IO を担当し、executor は work-stealing 方式で Future をマルチスレッドにスケジューリングします。これらを合わせたものが Rust における非同期処理の事実上の標準ランタイムです。spawn は Future をスレッドプールに送信し、select! は複数の非同期操作を同時に待機します。reactor と executor の役割分担を理解すれば、tokio のパフォーマンスモデルを理解したことになります。

tokio の3層アーキテクチャ

flowchart TD
    TASK["🧵 あなたの async task<br/>tokio::spawn"]

    TASK --> QUEUE["📋 Task Queue<br/>スケジューリング待ちの Future"]

    QUEUE --> EXECUTOR["⚙️ Executor<br/>work-stealing スケジューラ<br/>CPUコア1つにつき1つのワーカースレッド"]

    EXECUTOR --> REACTOR["🔌 Reactor<br/>epoll (Linux) / kqueue (macOS)<br/>IOCP (Windows)"]

    REACTOR --> KERNEL["🐧 OS カーネル"]

    REACTOR -.->|"IO 準備完了 → task をウェイクアップ"| QUEUE

    classDef user fill:#e3f2fd,stroke:#1565c0
    classDef middle fill:#fff3e0,stroke:#ef6c00
    classDef os fill:#f3e5f5,stroke:#7b1fa2
    class TASK,QUEUE user
    class EXECUTOR,REACTOR middle
    class KERNEL os

Reactor:登録されたすべての IO ソース(socket、timer、signal)を監視し、IO が準備完了になると、対応する task のウェイクアップを通知します。 Executor:task queue を管理し、ウェイクアップされた task に CPU タイムを再割り当てします。 あなたのコード⁠:async fn.await を呼び出すだけであり、reactor や executor と直接やり取りすることはありません。

2つのランタイム

// マルチスレッド (デフォルト)
#[tokio::main]
async fn main() { ... }

// 同等の記述:
fn main() {
    tokio::runtime::Builder::new_multi_thread()
        .worker_threads(num_cpus::get())  // デフォルト: CPU コア数
        .enable_all()                     // IO + タイマーを有効化
        .build().unwrap()
        .block_on(async { ... });
}

// シングルスレッド (単純なユースケース向け。スレッド間同期が不要な場合など)
#[tokio::main(flavor = "current_thread")]
async fn main() { ... }

spawn: 独立した並行タスク

let handle = tokio::spawn(async move {
    let result = do_heavy_work().await;
    println!("{}", result);
});
// handle: JoinHandle — .await して戻り値を取得可能
let result = handle.await.unwrap();

spawn されたタスクは異なるワーカースレッド上で実行される可能性があります。これは本物の並行処理であり、コルーチンではありません。返される JoinHandle は Future であり、.await することでタスクの完了を待機します。

Work Stealing スケジューラ

各ワーカースレッドには独自のローカルな task queue が存在します。あるワーカーがアイドル状態になると、他のワーカーのタスクをランダムに「盗み」ます。これは「1つのグローバルキュー + すべてのワーカーが競合する」モデルよりも効率的です。ローカルな push/pop はロックフリーであるため、ロック競合が減少します。

select!: 複数の Future を待機

tokio::select! {
    result = long_running_op() => { /* 結果を処理 */ }
    _ = tokio::signal::ctrl_c() => { /* Ctrl+C シグナル */ }
}

いずれかのブランチが完了すると、他のブランチはキャンセルされます(drop されます)。これは async Rust に組み込まれたキャンセルモデルです。明示的な cancellation token は不要です。キャンセルされた Future の Drop 実装がリソースのクリーンアップを担当します。

参考

  • tokio: docs.rs/tokio, tokio.rs
  • async book: rust-lang.github.io/async-book

Keywords: tokio, reactor, executor, work stealing, spawn, select!, runtime, current_thread