bigywater.tech
Rust · 107

Waker 唤醒机制

Waker 是异步 Rust 里最底层、最容易被忽略的一环:Future 返回 Pending 之后,靠它才能被再次 poll。这篇用自己写的 vtable 把 Waker 的四个操作逐个计数,再演示「忘了 wake」这个无声的挂起。

16 字节
Waker 的大小(数据指针 + vtable 指针)
4 个函数
RawWakerVTable 的函数数:clone、wake、wake_by_ref、drop
无声挂起
条件满足却忘了调用 wake 的 Future 的结局

Waker 的 vtable

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    size_of::<Waker>() = 16 字节(两个指针:数据指针 + vtable 指针)
    创建后:       clone 0 / wake 0 / wake_by_ref 0 / drop 0
    w.clone():     clone 1 / wake 0 / wake_by_ref 0 / drop 0
    w2.wake_by_ref(): clone 1 / wake 0 / wake_by_ref 1 / drop 0(不消耗 w2)
    w2.wake():     clone 1 / wake 1 / wake_by_ref 1 / drop 0(wake 消耗 w2,所以没有 drop 调用:由 wake 的实现负责释放)
    drop(w):       clone 1 / wake 1 / wake_by_ref 1 / drop 1

手写执行器里 Waker 的用法见Async Runtime 做了什么。

保存 waker

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      poll #1:Pending,保存了 waker(clone 1 / wake 0 / wake_by_ref 0 / drop 0)
      (外部事件调用 saved.wake())
      poll #2:Pending,保存了 waker(clone 2 / wake 1 / wake_by_ref 0 / drop 0)
      (外部事件调用 saved.wake())
      poll #3:Ready
    结束:clone 2 / wake 2 / wake_by_ref 0 / drop 0

will_wake

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    a.will_wake(&b)(b 是 a 的克隆): true
    a.will_wake(&c)(同一个数据指针 + 同一张 vtable,另一个实例): true
    Waker::noop().will_wake(&Waker::noop()): true
    a.will_wake(Waker::noop()): false

忘了 wake

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    注册并调用了 waker:Some(("就绪", 2))
    条件满足了但没调用 waker:None(500 ms 后放弃:任务永远睡下去)

手写完整 Future 见自定义 Future。

分步演示

Waker
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:Arc 构造的 Waker 的内存管理细节、跨线程 wake 的数据竞争、RawWaker 的 unsafe 契约被违反时的后果、LocalWaker(不稳定)、虚假唤醒下执行器的行为
    京ICP备2026067502号-1京公网安备11010502063487号