bigywater.tech
Rust · 97

Async Runtime 做了什么

说到异步运行时,很多人只会 #[tokio::main]。这篇自己写一个最小的执行器,把「任务队列、waker、poll 循环、定时器唤醒」这四个部件都亲手实现并跑通,再用它证明为什么异步任务里不能阻塞。

60 行
一个能交错运行异步任务的单线程执行器的代码量
150 ms
3 个 sleep 分别 150/50/100 ms 的任务总耗时(不是 300)
卡住
任务里调用 thread::sleep 时整个执行器的表现

执行器与交错运行

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    [  0 ms] 任务 A 开始(同一线程: true)
    [  0 ms] 任务 B 开始(同一线程: true)
    [  0 ms] 任务 C 开始(同一线程: true)
    [ 50 ms] 任务 B 睡了 50 ms,醒来结束
    [100 ms] 任务 C 睡了 100 ms,醒来结束
    [150 ms] 任务 A 睡了 150 ms,醒来结束
    总耗时约 150 ms(最长的任务 150 ms,而不是 150 + 50 + 100 = 300 ms);executor 一共 poll 了 6 次

Future 与 Poll 的基础见Future 与 Poll。

唤醒的路径

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      任务开始
      poll x → Pending,注册 waker(来自 "main")
      定时器线程 wake()
      任务被再次 poll,继续执行并结束
    executor 退出,共 poll 2 次

Waker 的细节见Waker 唤醒机制。

阻塞会卡住执行器

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    [  0 ms] 阻塞任务开始 thread::sleep(200)
    [200 ms] 阻塞任务结束
    [200 ms] 异步任务开始 sleep(20).await(期望 20 ms 后醒来)
    [220 ms] 异步任务醒来

更多见阻塞代码混入异步任务。

对照 tokio

实测 · 本机执行输出
   tokio current_thread:[150, 50, 100] 总耗时约 150 ms

tokio 的调度见Tokio 任务调度。tokio 1.x 从 crates.io 下载(需要联网)。

分步演示

异步运行时
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。手写执行器只用标准库;tokio 为 current_thread 运行时
    • 没有验证:多线程执行器的任务窃取、真实 I/O 的就绪通知(epoll/kqueue)、tokio 定时器的内部实现、手写版本在上万个任务下的开销、JoinHandle 与结果返回
    京ICP备2026067502号-1京公网安备11010502063487号