阻塞执行器

大多数异步运行时支持并发运行 IO 任务。这意味着 CPU 的阻塞性任务会阻塞执行器,并阻止执行其他任务。最简单的方法是,尽可能使用异步等效方法。

  1. use futures::future::join_all;
  2. use std::time::Instant;
  3. async fn sleep_ms(start: &Instant, id: u64, duration_ms: u64) {
  4. std::thread::sleep(std::time::Duration::from_millis(duration_ms));
  5. println!(
  6. "future {id} slept for {duration_ms}ms, finished after {}ms",
  7. start.elapsed().as_millis()
  8. );
  9. }
  10. #[tokio::main(flavor = "current_thread")]
  11. async fn main() {
  12. let start = Instant::now();
  13. let sleep_futures = (1..=10).map(|t| sleep_ms(&start, t, t * 10));
  14. join_all(sleep_futures).await;
  15. }
  • 运行该代码,您会发现休眠操作是连续发生的,而不是并发进行的。

  • "current_thread" 变种将所有任务放在单个线程上。这样做效果会更明显,但 bug 仍然存在于多线程变种中。

  • std::thread::sleep 切换为 tokio::time::sleep,并等待结果。

  • 另一个修复方案是 tokio::task::spawn_blocking,其会生成实际线程并将句柄转换为 Future,且不会阻塞执行器。

  • 不应将任务视为操作系统线程。它们之间并非一对一的映射关系,并且大多数执行器都支持在单个操作系统线程上运行多个任务。尤其是通过 FFI 与其他库交互时,会更容易出现问题,因为在 FFI 中,因为该库可能依赖于线程本地存储或映射到特定的操作系统线程(例如,CUDA)。在这些情况下,首选 tokio::task::spawn_blocking

  • 请谨慎使用同步互斥操作。对 .await 一直执行互斥操作能会导致另一个任务阻塞,并且该任务可能与其在同一线程上运行。