阻塞执行器
大多数异步运行时支持并发运行 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
一直执行互斥操作能会导致另一个任务阻塞,并且该任务可能与其在同一线程上运行。