ニュース

Async/Awaitは罠:なぜRustにはスレッド・パー・コアが必要なのか

Async/Awaitは複雑さを隠すが、本番環境では失敗する。新しいRustフレームワークTinaは、スレッド・パー・コアのアプローチでこれを修正する。

July 16, 2026· 1 min read
Async/Awaitは罠:なぜRustにはスレッド・パー・コアが必要なのか

Async/awaitは、記述が容易であるため並行性戦争に勝利した。しかし、Rich HickeyやRob Pikeが指摘したように、容易であることは単純であることではない。内部では、async/awaitはステートマシンを隠蔽し、ハードウェアの現実を曖昧にし、スケジューリングの複雑さを開発者に押し付ける。本番環境では、この抽象化はしばしば破滅的に崩壊する。

核心問題:非同期性と並行性

Async/awaitは、非同期性(I/Oでの待機)と並行性(複数のことを同時に処理すること)を混同している。開発者は逐次コードのように見える非同期関数を書くが、その関数が50msのCPUバウンドタスク(大きなJSONペイロードの解析や暗号証明の実行)を実行すると、協調的実行装置は停止する。TokioやNode.jsのようなランタイムでは、awaitポイントに到達するまでスレッドは譲らない。1つの重い計算タスクが、無関係な数千のリクエストのレイテンシを急上昇させ、システムを応答不能にする可能性がある。

人間参加型スケジューラ

これらのレイテンシスパイクが発生した場合、標準的な修正はランタイムを分離することである:I/OにはTokio、CPU処理にはRayonを使用する。しかし、これにより開発者はすべての関数を手動でI/Oプールと計算プールに分割し、それらの間でメッセージパッシングを調整し、デッドロックを防ぐために境界を監視することを強いられる。PostHogやMeilisearchの事後分析が示すように、これにより開発者は人間スケジューラと化す——まさにasync/awaitが排除するはずだったものだ。異なるメンタルモデルを持つ2つのランタイムを手動で管理しなければならないなら、抽象化は失敗している。

デフォルトで無制限はデフォルトでOOM

tokio::spawn(...)の呼び出しは安価であり、それが危険である。トラフィックスパイク中に下流のデータベースが遅くなると、イングレスループは接続を受け入れ続け、タスクを生成し続ける。非同期タスクとメモリ割り当ては通常無制限であるため、システムはプッシュバックしない。処理中のタスクは、OOMキラーがプロセスを終了するまで無期限にキューイングされる。キューは過負荷を修正せず、クラッシュを壊滅的にするだけで遅延させる。

ワークスティーリングの神話

システムがボトルネックに達すると、開発者はしばしば公平性のためにワークスティーリングスケジューラを要求する。しかし、大規模では公平性がスループットを破壊する。ワークスティーリングはステートマシンをCPUコア間で移動させ、L1/L2キャッシュを放棄し、100ナノ秒以上のメインメモリフェッチペナルティを発生させる。WhatsAppが100+コアマシン上のErlang BEAMで発見したように、アイドルスレッドがグローバル実行キューロックを巡って争うと、システムを窒息させる可能性がある。I/OとCPUのために手動でスレッドを分割することを既に強いられているなら、汎用のワークスティーリングアルゴリズムはあなたを失敗させている。

代替案:スレッド・パー・コア

ここにProject Tinaが登場する。これは、Rustのための意見を持った、共有なし、スレッド・パー・コアの並行性フレームワークである。ステートマシンを隠す代わりに、Tinaはそれを公開し、開発者により良い制御プリミティブを提供する。各コアは、独自のスケジューラループとアイソレート(例:TCP接続、HTTPハンドラ、ワーカー)を持つ1つのOSスレッドを実行する。ワークスティーリングはなく、ミューテックスもなく、厳格なキャッシュ局所性がある。クロスシャード通信はロックフリーのSPSCリングを介して行われる。これは、不透明なガベージコレクションやグローバルワークスティーリングなしで、BEAMのフォールトトレランスへの回帰である。

Tinaは、巨大なスループットと信頼性を保証するために厳格な制約を受け入れる。これは、時には最も単純なアーキテクチャ——コアあたり1スレッド、共有メモリなし——が最も堅牢であることを思い出させる。