源码地图
主线位置:四条主线经过的 crate 和文件。主线 1(一次 join)和主线 3(睡下又醒来)全在 rayon-core 里,主线 2(一次 par_iter)从 rayon 的
iter/出发,落到 rayon-core 的join_context。
flowchart BT utils["crossbeam-utils:只用 CachePadded"] epoch["crossbeam-epoch(经 deque 间接)"] deque["crossbeam-deque:Worker / Stealer / Injector / Steal"] --> epoch deque --> utils core["rayon-core 1.13.0:线程池、join、scope、spawn、sleep"] --> deque core --> utils rayon["rayon 1.12.0:并行迭代器、slice、排序"] --> core rayon --> either["either"] user["用户代码"] --> rayon
两层 crate
| crate | 版本 | 依赖 | src/ 行数 | 本卷 |
|---|---|---|---|---|
rayon-core | 1.13.0 | crossbeam-deque、crossbeam-utils;可选 wasm_sync | 7069,其中文件内测试和 compile_fail 约 2050 | 02–05 |
rayon | 1.12.0 | rayon-core、either;可选 wasm_sync | 27141,其中 iter/test.rs 2392 | 06、07 |
行数是 src/ 下 .rs 的总行数,含文档注释,不含 tests/。工作区声明的依赖下限是 crossbeam-deque = "0.8.1"、crossbeam-utils = "0.8.0"(根 Cargo.toml 的 [workspace.dependencies],注释说「Some dependencies may not be their latest version, in order to support older rustc」);本卷实验锁的是 0.8.8 / 0.8.23。rayon-core 没有用 crossbeam 的 Backoff,空转用的是 std::thread::yield_now(sleep/mod.rs:94-103)。
rayon 只碰 rayon-core 的哪几处
rayon 的 lib.rs 把 rayon-core 的公共 API 原样重新导出(lib.rs:112-123):join、join_context、scope、spawn、broadcast、ThreadPool、ThreadPoolBuilder、current_num_threads 等,所以 rayon::join 就是 rayon_core::join。rayon 自己的代码里实际调用 rayon-core 的地方很少,git grep 在非测试代码里能找全:
| 调用 | 位置 | 做什么 |
|---|---|---|
join_context | iter/plumbing/mod.rs:410、:465 | bridge_producer_consumer / bridge_unindexed 每切一刀 |
current_num_threads | plumbing/mod.rs:262、:273;iter/blocks.rs、iter/par_bridge.rs | Splitter 的初值和被偷后的重置值 |
join | iter/chain.rs;slice/sort.rs 三处 | chain 左右两半;排序的分治 |
current_thread_index | iter/par_bridge.rs | 防递归重入 |
in_place_scope | iter/skip.rs | skip 把被跳过的前缀 spawn 出去单独跑(为了副作用和 panic),当前线程继续处理剩下的部分 |
迭代器的所有并行都经 plumbing 的两个 bridge 函数落到 join_context;这就是 一次 par_iter 能画成一棵 join 树的原因。
rayon-core:每个文件一句话
| 文件 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
lib.rs | 864 | ThreadPoolBuilder 的字段与 setter、get_num_threads、废弃的 Configuration / initialize、ThreadPoolBuildError、FnContext、max_num_threads | ThreadPoolBuilder |
registry.rs | 1002 | Registry、全局池 THE_REGISTRY、WorkerThread、main_loop、找活顺序、in_worker 三条路径、inject / push、terminate | 02 全部 |
thread_pool/mod.rs | 502 | ThreadPool:install、join、scope、spawn、broadcast 都是转到 registry;Drop 只调 terminate;yield_now / yield_local | in_worker、yield |
job.rs | 270 | Job trait、JobRef(手写胖指针)、StackJob、HeapJob、ArcJob、JobFifo、JobResult | JobRef 与几种 Job |
latch.rs | 457 | Latch / AsCoreLatch、CoreLatch 四态、SpinLatch、LockLatch、OnceLatch、CountLatch、LatchRef | Latch 家族 |
join/mod.rs | 186 | join / join_context、A panic 后等 B 再传播 | join |
scope/mod.rs | 773 | scope / scope_fifo / in_place_scope、ScopeBase、panic 只保留第一个 | scope |
spawn/mod.rs | 163 | spawn / spawn_fifo:HeapJob + terminate_count | spawn |
broadcast/mod.rs | 150 | broadcast / spawn_broadcast、BroadcastContext | broadcast |
sleep/mod.rs | 324 | Sleep:start_looking、no_work_found、sleep、new_jobs、wake_* | 05 sleep 模块 |
sleep/counters.rs | 273 | 一个 AtomicUsize 打包 sleeping / inactive / JEC 三段;THREADS_MAX | 同上 |
unwind.rs | 31 | halt_unwinding(catch_unwind)、resume_unwinding、AbortIfPanic(析构时打印「Rayon: detected unexpected panic; aborting」并 abort) | spawn |
private.rs | 26 | private_decl! / private_impl!:让公开 trait 在 crate 外无法实现(ThreadSpawn 用到) | ThreadPoolBuilder |
compile_fail/*.rs | 141 | 7 个 doctest 形式的反例:quicksort_race1–3、rc_return、rc_upvar、scope_join_bad | scope 引一两个 |
*/test.rs、test.rs | 1907 | 文件内单元测试 | — |
非测试代码合计约 5000 行。registry.rs 一个文件就占了五分之一,它是本卷 02 部分的全部内容。
lib.rs 里还有一处按 feature 换实现(lib.rs:99-103):
#[cfg(not(feature = "web_spin_lock"))]
use std::sync;
#[cfg(feature = "web_spin_lock")]
use wasm_sync as sync;
rayon-core 内部所有的 Mutex / Condvar 都写成 crate::sync::…。开了 web_spin_lock 就换成 wasm_sync 的版本,Cargo.toml 的注释说它是为了在浏览器主线程上「switches to a spin-lock implementation … to avoid the forbidden atomics.wait」,只对 wasm32-unknown-unknown 有用。本卷不展开。
rayon:目录一句话
| 位置 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
iter/mod.rs | 3628 | ParallelIterator、IndexedParallelIterator、IntoParallelIterator 等 trait 的定义和文档 | 两个 trait |
iter/plumbing/ | 476 + README 315 | Producer / Consumer / Folder / Reducer,Splitter,bridge* | plumbing、切分 |
iter/ 其余 | 约 14700 | 每个适配器一个文件(map.rs、filter.rs、zip.rs…,多为 100–300 行);collect/、extend.rs、fold.rs、sum.rs、par_bridge.rs、walk_tree.rs 等 | 06 |
slice/ | 4233 | par_iter / par_chunks / par_windows 等 producer;sort.rs 1686 行 | 07 |
str.rs | 1006 | par_chars、par_split 等 | 只列表 |
range.rs、range_inclusive.rs | 474、398 | 整数区间的 IntoParallelIterator;u8–u32、usize、i8–i32、isize 是 Indexed,u64 / i64 / u128 / i128 不是 Indexed,但长度装得进 usize 时内部改走 (0..len).into_par_iter().map(..) 的 Indexed 路径 | 一次 par_iter |
collections/、vec.rs、option.rs、array.rs 等 | — | 标准容器的 IntoParallelIterator | 只列表 |
links = "rayon-core":一个程序只能有一份
rayon-core 的 Cargo.toml 有两行不寻常的设置:
links = "rayon-core"
build = "build.rs"
build.rs 什么也不做(build.rs):
// We need a build script to use `link = "rayon-core"`. But we're not
// *actually* linking to anything, just making sure that we're the only
// rayon-core in use.
fn main() {
// we don't need to rebuild for anything else
println!("cargo:rerun-if-changed=build.rs");
}
Cargo 规定同一个 links 值在一张依赖图里只能出现一次。所以就算依赖树里有两个 semver 不兼容的 rayon-core 版本要求,构建也会失败,而不是悄悄编进两份。为什么要这么严:全局池是 registry.rs 里的一个 static(见 全局池与 Registry),两份 rayon-core 就是两个全局池,各自按 CPU 数开线程,current_num_threads() 各说各的。反过来,只要 rayon-core 只有一份,依赖不同 rayon 版本的库共享同一个全局池。crate 文档里写了违反时的报错原文(lib.rs:46-49):
error: native library `rayon-core` is being linked to by more
than one package, and can only be linked to by one package
CI 用 ci/highlander.sh 验证这条约束(脚本开头打印「There Can Be Only One!」):ci/alt-core 是一个空 crate,同样声明 links = "rayon-core";ci/highlander 同时依赖 alt-core 和真正的 rayon-core。脚本期待这个构建失败,构建成功反而报错「we built with multiple rayon-core!」。
测试和 demo
| 目录 | 是什么 |
|---|---|
rayon-core/tests/ | 需要独立进程的集成测试:double_init_fail.rs(两次 build_global,第二次的报错原文)、init_zero_threads.rs(num_threads(0) 走默认值)、use_current_thread.rs、scoped_threadpool.rs(build_scoped + scoped TLS)、stack_overflow_crash.rs(stack_size 设小时 worker 栈溢出让子进程崩溃,设大时正常结束)、simple_panic.rs、scope_join.rs |
tests/ | rayon 本体的集成测试,例如 cross-pool.rs(跨池阻塞时 latch 被 set 后不能再访问 job)、octillion.rs(0u128..10²⁷ 这种超过 usize 的区间)、par_bridge_recursion.rs、sort-panic-safe.rs |
rayon-demo/ | 基准和示例程序:join_microbench.rs、cpu_time/(测空闲 CPU 时间)、fibonacci/、quicksort/、nbody/、life/ 等;本卷只引用 cpu_time 的测法和 join_microbench 的形状 |
ci/ | highlander、alt-core、compat-Cargo.lock(MSRV 构建用的锁文件) |
这些测试本卷只当资料读,不在上游目录里运行;所有实测都在 exp-rayon/ 的独立 crate 里做。
两份上游 README
源码目录里有两份说明文档:
rayon-core/src/sleep/README.md(219 行):开头说本模块的设计来自 Rayon RFC #5,「the comments in this file are extracted from the RFC and meant to be kept up to date」,同时承认它们「will also grow stale over time」。src/iter/plumbing/README.md(315 行):Producer / Consumer 两种驱动方式的说明。
这两份都是资料。本卷以代码为准,发现 README 和代码不一致时在页面里指出。
小结
- 两层:rayon-core(约 5000 行非测试代码,
registry.rs一千行)是线程池;rayon(2.7 万行,大头是iter/)是迭代器。rayon 对 rayon-core 的实际调用只有join_context、join、current_num_threads等几处。 - rayon-core 只直接依赖 crossbeam-deque 和 crossbeam-utils(只用
CachePadded),不用Backoff。 links = "rayon-core"加一个空的build.rs,保证一个程序里只有一份 rayon-core、一个全局池;ci/highlander用一个假的同名linkscrate 验证这一点。web_spin_lock把内部的Mutex/Condvar换成wasm_sync,只为浏览器主线程。