エグゼキュータをブロックする
ほとんどの async ランタイムでは、並行して実行できるのは I/O タスクだけです。これは、CPU をブロックするタスクがエグゼキュータをブロックし、ほかのタスクの実行を妨げることを意味します。簡単な回避策は、可能な場合は async 相当のメソッドを使うことです。
// 著作権 2024 Google LLC // SPDX-License-Identifier: Apache-2.0 use futures::future::join_all; use std::time::Instant; async fn sleep_ms(start: &Instant, id: u64, duration_ms: u64) { std::thread::sleep(std::time::Duration::from_millis(duration_ms)); println!( "future {id} slept for {duration_ms}ms, finished after {}ms", start.elapsed().as_millis() ); } #[tokio::main(flavor = "current_thread")] async fn main() { let start = Instant::now(); let sleep_futures = (1..=10).map(|t| sleep_ms(&start, t, t * 10)); join_all(sleep_futures).await; }
-
コードを実行し、sleep が並行ではなく連続して発生することを確認してください。
-
"current_thread"フレーバーでは、すべてのタスクが単一のスレッドに配置されます。これによりこの効果はより分かりやすくなりますが、このバグはマルチスレッドのフレーバーでも依然として存在します。 -
std::thread::sleepをtokio::time::sleepに置き換え、その結果を await してください。 -
別の修正方法としては
tokio::task::spawn_blockingを使うことです。これは実際のスレッドを生成し、そのハンドルをエグゼキュータをブロックすることなく future に変換します。 -
タスクを OS スレッドとして考えるべきではありません。両者は 1 対 1 には対応せず、エグゼキュータでは複数のタスクを単一の OS スレッド上で実行できます。これは、FFI 経由で他のライブラリとやり取りする場合に特に問題になります。そのライブラリがスレッドローカルストレージに依存していたり、特定の OS スレッドにマップされたりする可能性があるためです(例: CUDA)。そのような状況では
tokio::task::spawn_blockingを優先してください。 -
同期ミューテックスは注意して使用してください。
.awaitをまたいでミューテックスを保持すると、別のタスクがブロックされる可能性があり、そのタスクは同じスレッド上で実行されているかもしれません。