Skip to content

定时器与时间轮

This content is not available in your language yet.

定时器的难点不只是“到时间 wake”,而是大量 deadline 的插入、重置、取消,以及如何让 runtime 既不忙轮询,也不会睡过最近到期时间。Tokio 用分层时间轮管理条目,并让 time driver 包裹底层 park。

先给答案:定时器的难点不只是“到时间 wake”,而是大量 deadline 的插入、重置、取消,以及如何让 run…

Section titled “先给答案:定时器的难点不只是“到时间 wake”,而是大量 deadline 的插入、重置、取消,以及如何让 run…”

定时器的难点不只是“到时间 wake”,而是大量 deadline 的插入、重置、取消,以及如何让 runtime 既不忙轮询,也不会睡过最近到期时间。Tokio 用分层时间轮管理条目,并让 time driver 包裹底层 park。 正文沿“Sleep 调用链 -> 分层时间轮 -> time driver 如何控制 park”展开:先确认入口和状态归属,再跟踪控制流或数据流的推进,最后落到对外可观察的结果。

主要失效边界集中在“未启用 time driver 就创建 Sleep、把 sleep 当精确定时、interval 任务处理过慢”这些场景。它们破坏的是容量、顺序、并发或生命周期前提;排查时应先确认状态是否仍由正确对象持有,再核对推进条件和清理路径。

sleep(duration)
-> sleep_until(deadline) sleep.rs:62 / 123
-> Sleep { inner: TimerEntry } sleep.rs:225
-> Future::poll sleep.rs:452
-> TimerEntry::poll_elapsed
-> 注册 Waker / 提交 deadline
-> Pending
time driver 到期
-> Wheel::poll
-> process_expiration
-> TimerEntry::fire
-> wake task

Sleep::reset 位于 tokio/src/time/sleep.rs:344-366,允许复用同一 Future 改变 deadline;这要求旧位置从时间轮逻辑上失效,不能让旧到期事件错误完成新 deadline。

Wheel 定义于 tokio/src/runtime/time/wheel/mod.rs:22-39,poll_at、poll 和 process_expiration 分别位于 wheel/mod.rs:137-235。

near deadline -> 低层槽:粒度细,旋转快
far deadline -> 高层槽:覆盖范围大,粒度粗
时间推进
-> 找到下一到期层和槽
-> 取出该槽条目
-> 已到期:fire
-> 尚未到期:按剩余时间重新分层

相较最小堆,时间轮让批量插入和删除更接近 O(1),代价是时间离散化、层级计算和边界处理更复杂。

time Driver 定义于 tokio/src/runtime/time/mod.rs:90-118。park_internal 位于 time/mod.rs:213-275:

wheel.next_expiration_time()
-> 计算最近 deadline 与 now 的差
-> min(调用方 park limit, timer duration)
-> 底层 IO driver.park_timeout(duration)
-> 唤醒后 process(now)

没有 timer 时可以无限 park;有 timer 时最多睡到最近 deadline。这样 IO 事件、新任务和定时器共同决定 worker 的唤醒时刻,而不是各占一个轮询线程。

Handle::process 位于 time/mod.rs:290-345。它按当前 tick 推进 wheel,把到期条目的 Waker 收集进 WakeList,释放时间轮锁后再唤醒,避免任务回入时与 driver 锁形成死锁。

时间倒退时会钳制到 wheel 已经过的时间;测试 feature 还支持暂停和自动推进虚拟时钟,使定时测试不依赖真实等待。

方案 优点 代价
每定时器一个线程 实现直观 线程和唤醒成本不可接受
最小堆 最近 deadline 精确 大量取消/重置维护堆位置
分层时间轮 批量计时器吞吐高 粒度、跨度和层级逻辑复杂
场景 风险 处理
未启用 time driver 就创建 Sleep 运行时 panic Builder 启用 time 或 enable_all
把 sleep 当精确定时 OS 调度和 worker 忙碌会延迟 把 deadline 视为最早唤醒时间
interval 任务处理过慢 tick 可能积压或跳过 明确 missed tick 策略
Future 长时间占用 worker timer 已到期但无法被 poll 保持协作调度或移出 CPU 工作

计时器应与执行器的 park deadline 合并:事件循环只需要一次阻塞等待,由最近定时任务给出上限。到期只负责唤醒,业务回调仍回到普通调度器执行。

面试锚点

  • Tokio 为什么使用分层时间轮而不是每次扫描所有 timer?
  • time driver 与 IO driver 如何共享线程等待?
  • timer 到期为什么不等于 Future 立即继续执行?