任务与 Latch
rayon-core 的线程池里流动的只有两样东西:任务和信号。任务是 deque 里的一个元素,谁拿到谁执行;信号是「那个任务做完了」,等的人看到它才能往下走。前者是 job.rs(270 行),后者是 latch.rs(457 行)。这两个文件都是 pub(super),用户看不到,但 join、scope、spawn、broadcast、install 全都由它们拼成。
flowchart LR
subgraph caller["发起方的栈帧"]
sj["StackJob:latch + func + result"]
end
sj -->|"as_job_ref:擦掉类型"| jr["JobRef:pointer + execute_fn,16 字节"]
jr -->|"push / inject"| dq["deque / Injector"]
dq -->|"pop / steal"| ex["执行方:execute_fn(pointer)"]
ex -->|"写 result,再 Latch::set"| sj
caller -.->|"wait_until(latch):边等边干活"| sj
两章:
- JobRef 与几种 Job:
Jobtrait 只有一个unsafe fn execute(*const ());JobRef是两个字的手写胖指针,类型和生命周期一起被擦掉,deque 里只需要一种元素类型;StackJob(join、install、broadcast)、HeapJob(spawn、scope.spawn)、ArcJob(spawn_broadcast)、JobFifo(spawn_fifo的间接层)各自在哪里、什么时候释放;JobResult三态;migrated参数实际表示什么(实验 E14)。 - Latch 家族:
Latch::set为什么收*const Self;CoreLatch的UNSET / SLEEPY / SLEEPING / SET四态,以及它们怎么让「设置」和「睡下」两个动作在同一个原子变量上分出先后;SpinLatch、LockLatch、OnceLatch、CountLatch、LatchRef各给谁用;每个set实现里「先读字段、后 set」的顺序。
一张对照表
| 用户 API | 任务类型 | 放在哪 | 等待方用的 latch | 等待方式 |
|---|---|---|---|---|
join 的 B | StackJob | 本线程 deque | SpinLatch | wait_until:边等边找活 |
池外调 install / join / scope | StackJob | 全局 Injector | LatchRef<LockLatch>(线程本地) | Condvar 阻塞 |
在别的池里调 install | StackJob | 目标池的 Injector | SpinLatch::cross | 在自己的池里边等边干活 |
scope.spawn | HeapJob | 本线程 deque 或 Injector | CountLatch | 等的线程是 worker 就 wait_until;池外的 in_place_scope 用 LockLatch |
spawn | HeapJob | 同上 | 没有,没人等 | — |
spawn_fifo | HeapJob 进 JobFifo,deque 里放指向 FIFO 的 JobRef | 本线程 deque 或 Injector | 没有 | — |
broadcast | 每线程一个 StackJob | 每线程的广播队列 | CountLatch(经 LatchRef) | 同 scope |
spawn_broadcast | 一个 ArcJob,每线程一个 JobRef | 每线程的广播队列 | 没有 | — |
| 线程池终止 | — | — | OnceLatch(每线程一个 terminate) | 主循环 wait_until |
这张表的每一行在 04 对外 API 里展开。等待方怎么「边等边干活」见 WorkerThread 主循环;等不到活之后怎么睡、SLEEPY / SLEEPING 两个状态怎么用,见 05 sleep 模块。
本部分的实验
| # | 目录 | 看什么 | 章 |
|---|---|---|---|
| E14 | exp-rayon/join-context/ | RAYON_NUM_THREADS=1 下所有闭包都在 0 号线程上跑,migrated() 仍可能是 true:从池外调用时 A、B 都是 true;A 里调 yield_local() 弹出 B 时 B 是 true | JobRef 与几种 Job |
两章大部分内容靠读代码,实验只有这一个。latch 的正确性(「set 之后不碰」)没有办法用一次运行证明:读错了的版本在大多数运行里也不会出错,上游是靠 miri 发现的(见 Latch 家族里 a46e1f8 那一段)。
💬 关联讨论
发起讨论加载中...