Rust in Everything
基于 rayon 1.12.0

rayon 源码解析

全卷对着一个 tag 写:

crate版本发布源码链接用的 tag
rayon1.12.02026-04-13v1.12.0(c9ced18)
rayon-core1.13.02025-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_now 32 轮,宣布 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 不能绑核,线程会在性能核和能效核之间迁移。每个实验带一个反向验证。细节见 怎么读这一卷。

各部分

  1. 00 导读:版本、四条主线、阅读路线、实验方法;两个 crate 的分层和每个文件管什么。
  2. 01 全景:rayon 解决什么问题、不适合什么;一次 join、一次 par_iter、一个线程睡下又醒来,各走一遍。
  3. 02 线程池与 Registry:ThreadPoolBuilder、全局池的懒初始化、WorkerThread 主循环与找活顺序、从池外 / 池内 / 别的池进池的三条路径。
  4. 03 任务与 Latch:JobRef 手写胖指针、几种 Job;Latch 家族和 CoreLatch 四态。
  5. 04 对外 API:join、scope、spawn、broadcast、yield_now。
  6. 05 sleep 模块:本卷核心。打包计数器、JEC 奇偶编码、睡眠与唤醒怎么不丢信号、实测空转和唤醒延迟。
  7. 06 并行迭代器:两个 trait、plumbing、自适应切分、适配器、collect、fold 与 reduce、par_bridge。
  8. 07 slice 与排序:slice producer、par_sort 与 par_sort_unstable。
  9. 08 设计取舍:把各章的取舍汇总到一起。
  10. 09 常见坑:用报错原文或现象做标题。
  11. 10 在本站中:本站经 tantivy、image 间接依赖的 rayon。
  12. 11 动手:最小工作窃取池、最小 join、最小自适应 Splitter。

从「导读」开始。只想用好 rayon 的读者,读 01、04、06、09;想读懂调度器的,读 02、03、05;想读懂迭代器的,读 06、07。

💬 关联讨论

发起讨论
加载中...