Rust in Everything
基于 tokio 1.54.1

调度器

上一部分讲到,任务对调度器只暴露一个接口:拿到 Notified 就调 run()。这一部分回答剩下的问题:Notified 放进哪个队列,哪个线程在什么时候把它取出来。

源码在 runtime/scheduler/。两种调度器各有一个目录,current_thread/ 只有一个 900 多行的文件,multi_thread/ 有 3000 多行,是本卷篇幅最大的部分。

  1. current_thread:单线程调度器,作为热身。一个 VecDeque 加一个注入队列,block_on 的循环。
  2. multi_thread 总览:Worker、Core、Shared 各管什么;Box<Core> 为什么能在线程之间交接。
  3. 本地队列:容量 256 的环形缓冲,一个生产者、多个窃取者,全靠两次 CAS。
  4. 全局注入队列:带锁的侵入式链表;worker 多久看它一次,这个间隔怎么按任务耗时自动调节。
  5. worker 主循环:Context::run、run_task 和 LIFO 槽;每 61 次 tick 做一次的维护。
  6. 任务窃取与空闲:同时找活的 worker 不超过一半;叫醒谁、什么时候睡。
  7. 协作式调度预算:每次 poll 128 的预算,用完后 tokio 自己的 future 返回 Pending。
  8. 阻塞线程池:spawn_blocking 的线程池;block_in_place 怎么把 Core 交给新线程。

一个 Notified 在多线程调度器里可能经过的地方:

flowchart TB
  spawn[spawn 或 wake] --> where{当前线程是本运行时的 worker,且持有 Core?}
  where -->|是| lifo[LIFO 槽]
  lifo -->|被新任务挤出| local[本地队列,256 个槽]
  local -->|满了,一半溢出| inject[全局注入队列]
  where -->|否| inject
  local -->|空闲 worker 偷走一半| other[别的 worker 的本地队列]
  inject -->|按间隔或本地空了时取一批| local
  lifo --> run[run_task:Harness::poll]
  local --> run
  inject --> run

读这一部分时,记住任务那一部分的一个结论:Notified 只有一个。NOTIFIED 位保证同一个任务同一时刻最多出现在一个队列里,所以上面这些队列之间挪来挪去,挪的永远是唯一的那个引用。

💬 关联讨论

发起讨论
加载中...