rayon 源码解析
全卷对着一个 tag 写:
| crate | 版本 | 发布 | 源码链接用的 tag |
|---|---|---|---|
rayon | 1.12.0 | 2026-04-13 | v1.12.0(c9ced18) |
rayon-core | 1.13.0 | 2025-08-12,与 rayon 1.11.0 一起 | 同上;rayon-core/ 目录与 tag rayon-core-v1.13.0 逐字节相同 |
rayon 是 Rust 的数据并行库。用户看到的是「把 iter() 改成 par_iter()」,底下是两层:
- rayon-core:一个工作窃取线程池。对外只有十来个函数和类型:
join、scope、spawn、broadcast、ThreadPool、ThreadPoolBuilder、yield_now。每个工作线程有一个 crossbeam-deque 的Worker,空闲时去别的线程那里偷任务,再空闲就按一套计数器协议睡下。 - rayon:并行迭代器、并行 slice 和排序。它自己不开一个线程,所有并行最终都落到
rayon_core::join_context:一个par_iter().sum()就是一棵二分递归的join树,树切多深由「被偷了没有」动态决定。
它和前几卷的关系:crossbeam 卷 是直接前置,rayon-core 的 deque、注入队列、CachePadded 都来自那里,本卷只链过去不重讲。tokio 卷 讲的是 IO 密集的 async 运行时,它的文档建议 CPU 密集的活交给「rayon 这类专用池」;本卷讲的就是那个池。tantivy、candle、burn、tenferro 各卷里都有 rayon 的用法和实测现象,本卷用源码解释它们。
全卷沿四条线展开:
- 一次 join:
join(a, b)把 B 包成栈上的StackJob推进本地 deque,自己跑 A,回头再找 B:B 还在就自己跑,被偷了就边等边干别的活。 - 一次
par_iter().sum():bridge把 Range 变成 Producer,LengthSplitter决定切不切,每切一次就是一次join_context,叶子上顺序折叠,回溯时归约。 - 一个线程睡下又醒来:找不到活先
yield_now32 轮,宣布 sleepy,再找一轮,然后在Condvar上睡;发布任务的一方看计数器决定叫醒几个。 - 内存序与「设置之后不能再碰」横线:每个
Ordering和谁配对;Latch::set之后,latch 所在的栈帧随时可能消失。
flowchart TB intro[00 导读] --> overview[01 全景] overview --> reg[02 线程池与 Registry] reg --> jl[03 任务与 Latch] jl --> api[04 对外 API] jl --> sleep[05 sleep 模块] overview --> pi[06 并行迭代器] pi --> ss[07 slice 与排序] api --> design[08 设计取舍] sleep --> design ss --> design design --> pit[09 常见坑] pit --> site[10 在本站中] site --> hands[11 动手]
实验环境
本机 Apple M2 Max(8 个性能核 + 4 个能效核,共 12 个逻辑核),macOS,rustc 1.99.1。实验程序在 exp-rayon/ 下,依赖锁定 rayon =1.12.0、rayon-core =1.13.0、crossbeam-deque =0.8.8。性能类一律 release、每组至少 3 次取中位数,测前看 1 分钟负载并写在页面上;macOS 不能绑核,线程会在性能核和能效核之间迁移。每个实验带一个反向验证。细节见 怎么读这一卷。
各部分
- 00 导读:版本、四条主线、阅读路线、实验方法;两个 crate 的分层和每个文件管什么。
- 01 全景:rayon 解决什么问题、不适合什么;一次 join、一次 par_iter、一个线程睡下又醒来,各走一遍。
- 02 线程池与 Registry:
ThreadPoolBuilder、全局池的懒初始化、WorkerThread主循环与找活顺序、从池外 / 池内 / 别的池进池的三条路径。 - 03 任务与 Latch:
JobRef手写胖指针、几种 Job;Latch 家族和CoreLatch四态。 - 04 对外 API:
join、scope、spawn、broadcast、yield_now。 - 05 sleep 模块:本卷核心。打包计数器、JEC 奇偶编码、睡眠与唤醒怎么不丢信号、实测空转和唤醒延迟。
- 06 并行迭代器:两个 trait、plumbing、自适应切分、适配器、collect、fold 与 reduce、
par_bridge。 - 07 slice 与排序:slice producer、
par_sort与par_sort_unstable。 - 08 设计取舍:把各章的取舍汇总到一起。
- 09 常见坑:用报错原文或现象做标题。
- 10 在本站中:本站经 tantivy、image 间接依赖的 rayon。
- 11 动手:最小工作窃取池、最小 join、最小自适应 Splitter。
从「导读」开始。只想用好 rayon 的读者,读 01、04、06、09;想读懂调度器的,读 02、03、05;想读懂迭代器的,读 06、07。
💬 关联讨论
发起讨论加载中...