Rust in Everything
基于 rayon 1.12.0

源码地图

主线位置:四条主线经过的 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-core1.13.0crossbeam-deque、crossbeam-utils;可选 wasm_sync7069,其中文件内测试和 compile_fail 约 205002–05
rayon1.12.0rayon-core、either;可选 wasm_sync27141,其中 iter/test.rs 239206、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_contextiter/plumbing/mod.rs:410、:465bridge_producer_consumer / bridge_unindexed 每切一刀
current_num_threadsplumbing/mod.rs:262、:273;iter/blocks.rs、iter/par_bridge.rsSplitter 的初值和被偷后的重置值
joiniter/chain.rs;slice/sort.rs 三处chain 左右两半;排序的分治
current_thread_indexiter/par_bridge.rs防递归重入
in_place_scopeiter/skip.rsskip 把被跳过的前缀 spawn 出去单独跑(为了副作用和 panic),当前线程继续处理剩下的部分

迭代器的所有并行都经 plumbing 的两个 bridge 函数落到 join_context;这就是 一次 par_iter 能画成一棵 join 树的原因。

rayon-core:每个文件一句话

文件行数管什么本卷
lib.rs864ThreadPoolBuilder 的字段与 setter、get_num_threads、废弃的 Configuration / initialize、ThreadPoolBuildError、FnContext、max_num_threadsThreadPoolBuilder
registry.rs1002Registry、全局池 THE_REGISTRY、WorkerThread、main_loop、找活顺序、in_worker 三条路径、inject / push、terminate02 全部
thread_pool/mod.rs502ThreadPool:install、join、scope、spawn、broadcast 都是转到 registry;Drop 只调 terminate;yield_now / yield_localin_worker、yield
job.rs270Job trait、JobRef(手写胖指针)、StackJob、HeapJob、ArcJob、JobFifo、JobResultJobRef 与几种 Job
latch.rs457Latch / AsCoreLatch、CoreLatch 四态、SpinLatch、LockLatch、OnceLatch、CountLatch、LatchRefLatch 家族
join/mod.rs186join / join_context、A panic 后等 B 再传播join
scope/mod.rs773scope / scope_fifo / in_place_scope、ScopeBase、panic 只保留第一个scope
spawn/mod.rs163spawn / spawn_fifo:HeapJob + terminate_countspawn
broadcast/mod.rs150broadcast / spawn_broadcast、BroadcastContextbroadcast
sleep/mod.rs324Sleep:start_looking、no_work_found、sleep、new_jobs、wake_*05 sleep 模块
sleep/counters.rs273一个 AtomicUsize 打包 sleeping / inactive / JEC 三段;THREADS_MAX同上
unwind.rs31halt_unwinding(catch_unwind)、resume_unwinding、AbortIfPanic(析构时打印「Rayon: detected unexpected panic; aborting」并 abort)spawn
private.rs26private_decl! / private_impl!:让公开 trait 在 crate 外无法实现(ThreadSpawn 用到)ThreadPoolBuilder
compile_fail/*.rs1417 个 doctest 形式的反例:quicksort_race1–3、rc_return、rc_upvar、scope_join_badscope 引一两个
*/test.rs、test.rs1907文件内单元测试—

非测试代码合计约 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.rs3628ParallelIterator、IndexedParallelIterator、IntoParallelIterator 等 trait 的定义和文档两个 trait
iter/plumbing/476 + README 315Producer / 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/4233par_iter / par_chunks / par_windows 等 producer;sort.rs 1686 行07
str.rs1006par_chars、par_split 等只列表
range.rs、range_inclusive.rs474、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只列表

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 用一个假的同名 links crate 验证这一点。
  • web_spin_lock 把内部的 Mutex / Condvar 换成 wasm_sync,只为浏览器主线程。

💬 关联讨论

发起讨论
加载中...