エグゼキュータのブロック
ほとんどの非同期ランタイムは、IO タスクの同時実行のみを許可します。つまり、CPU ブロックタスクはエグゼキュータをブロックし、他のタスクの実行を妨げます。簡単な回避策は、可能であれば非同期の同等のメソッドを使用することです。
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;
}
-
コードを続けて、スリープが同時ではなく連続して発生することを確認します。
-
"current_thread"フレーバーは、すべてのタスクを 1 つのスレッドに配置します。これにより、影響はより明確になりますが、バグはまだマルチスレッド フレーバーに存在します。 -
std::thread::sleepをtokio::time::sleepに切り替えて、その結果を待ちます。 -
もう 1 つの修正策は、
tokio::task::spawn_blockingを使用することです。これは、実際のスレッドを生成し、エグゼキュータをブロックせずにそのハンドルを Future に変換します。 -
タスクは OS スレッドとはみなすべきではありません。これらは 1 対 1 に対応しておらず、ほとんどのエグゼキュータは、単一の OS スレッドで多くのタスクを実行することを許可します。これは、FFI を介して他のライブラリとやり取りする場合に特に問題となります。FFI では、そのライブラリはスレッド ローカル ストレージに依存しているか、特定の OS スレッド(CUDA など)にマッピングされている可能性があるためです。そのような場合は
tokio::task::spawn_blockingを使用することをおすすめします。 -
同期ミューテックスは慎重に使用してください。
.awaitでミューテックスを保持すると、別のタスクがブロックされ、そのタスクが同じスレッドで実行される可能性があります。