Rust in Everything
基于 tokio 1.54.1

定时器

tokio::time::sleep 返回的 Sleep 本身不会计时。计时的是运行时里的时间驱动:一个分层时间轮,夹在驱动栈里 IO 驱动的上面。worker 没活干去 park 时,时间驱动先算出最近一个定时器还有多久到期,用它当 park 的超时;醒来后把到期的定时器取出来,挨个调 waker。源码分两层:time/ 是用户层的 Sleep、Timeout、Interval,runtime/time/ 是时间轮和驱动。

这一部分分三页:

  1. 分层时间轮:6 层、每层 64 个槽,覆盖约 2.18 年;插入时用 elapsed ^ when 一步算出层号;时间推进时高层的定时器逐层下放;驱动怎么从下一个到期时间算出 park 超时。
  2. Sleep 的一生:创建时不碰驱动,第一次 poll 才登记;一个 u64 状态字段同时存到期时间和「已触发」标记;drop 时取消;timeout 怎么绕开预算;interval 错过 tick 后的三种补法。
  3. (unstable)time_alt:每个 worker 一个本地时间轮,登记、取消、触发都不用抢全局锁;代价是定时器只能由登记它的那个 worker 来触发。
flowchart TB
  new[sleep:只记下 deadline] --> poll1[第一次 poll]
  poll1 --> reg[加锁,插入时间轮]
  reg --> pend[返回 Pending]
  pend --> park[worker park:超时取自时间轮]
  park --> proc[醒来,process 到期槽位]
  proc --> fire[fire:状态改成已触发,取出 waker]
  fire --> wake[wake,任务重新进队列]
  wake --> poll2[再次 poll:Ready]

驱动栈怎么一层层套起来、park 和 park_timeout 的分工,见驱动栈与 park。

💬 关联讨论

发起讨论
加载中...