阻塞执行器
大多数异步运行时支持并发运行 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"
变种将所有任务放在单个线程上。这样做效果会更明显,但 bug 仍然存在于多线程变种中。将
std::thread::sleep
切换为tokio::time::sleep
,并等待结果。另一个修复方案是
tokio::task::spawn_blocking
,其会生成实际线程并将句柄转换为 Future,且不会阻塞执行器。不应将任务视为操作系统线程。它们之间并非一对一的映射关系,并且大多数执行器都支持在单个操作系统线程上运行多个任务。尤其是通过 FFI 与其他库交互时,会更容易出现问题,因为在 FFI 中,因为该库可能依赖于线程本地存储或映射到特定的操作系统线程(例如,CUDA)。在这些情况下,首选
tokio::task::spawn_blocking
。请谨慎使用同步互斥操作。对
.await
一直执行互斥操作能会导致另一个任务阻塞,并且该任务可能与其在同一线程上运行。