crossbeam 解决什么问题
主线位置:三条主线之前。本页说明 crossbeam 是什么、它的哪些部分已经被标准库吸收、哪些仍然只能在这里找到,以及前面几卷里谁在用它。
flowchart LR
subgraph std["后来进了 std"]
scope["thread::scope:1.63.0"]
mpsc["sync::mpsc 换成 crossbeam-channel 的移植:1.67.0"]
mpmc["sync::mpmc:仍是 unstable"]
end
subgraph only["仍只在 crossbeam"]
deque[deque]
epoch[epoch]
ac[AtomicCell]
sel["select!、tick、at"]
q["ArrayQueue、SegQueue"]
sk[skiplist]
end
是工具箱,不是运行时
crossbeam 不调度任何东西:没有线程池,没有事件循环,没有 async。门面 crate crossbeam 的 src/lib.rs 只有 84 行,全是重导出;README 把内容分成五组:
| 组 | 内容 | 所在 crate |
|---|---|---|
| 原子 | AtomicCell、AtomicConsume | crossbeam-utils |
| 数据结构 | 工作窃取双端队列 deque、有界 ArrayQueue、无界 SegQueue | crossbeam-deque、crossbeam-queue |
| 内存管理 | 基于纪元的回收 epoch | crossbeam-epoch |
| 线程同步 | MPMC channel(含 select!)、Parker、ShardedLock、WaitGroup | crossbeam-channel、crossbeam-utils |
| 工具 | Backoff、CachePadded、作用域线程 scope | crossbeam-utils |
还有一个不在门面里的 crossbeam-skiplist,README 称之为「experimental」,发布分支的工作区甚至把它从成员里注释掉了(「Not published from this branch」,源码地图)。
每一块都可以单独依赖:rayon 只要 deque 和 utils,tantivy 只要 channel,sqlx 只要 queue。这是它和 tokio 最大的区别:tokio 是一个运行时,各模块共享调度器和驱动;crossbeam 的各个 crate 之间只有「utils 是地基、epoch 给 deque 和 skiplist 管内存」这层关系。
当初标准库缺什么
仓库的第一个提交是 2015-05-13,同一天就有一个叫「Basic scoped threads」的提交;三个月后(2015-08-03)出现「Initial draft of epoch reclaimation scheme」。现在的工作区布局是 2018 年底才成形的:2018-10-26 utils 搬进自己的目录,2018-11-05 的 d9b1e342「Merge remaining subcrates」把 channel、deque、epoch、skiplist 并进来(在这之前它们的历史不在本仓库里),queue 在 2019-01 拆成单独的 crate。到发布提交 2920c984 为止共 2173 个提交。
从这条时间线和 API 能看出它在补的几块空白:
| crossbeam 提供的 | 当时 std 的情况 | 现在 std 的情况(1.99.1) |
|---|---|---|
作用域线程 crossbeam::scope | 没有 | std::thread::scope,1.63.0 稳定;crossbeam 文档 2023-01 起把自己的版本标为 soft-deprecated(thread::scope) |
MPMC channel,Sender / Receiver 都可克隆 | std::sync::mpsc 只有单消费者,Receiver 不能克隆 | std::sync::mpsc 自 1.67.0 起改用 crossbeam-channel 的移植(rust-lang/rust#93563);多消费者的 std::sync::mpmc 仍是 unstable(mpmc_channel,issue 126840),oneshot 也是(oneshot_channel,issue 143674) |
select! 同时等多个 channel | 没有 | 仍然没有。std 移植时把 select 删掉了 |
定时器 channel tick / after / at / never | 没有 | 仍然没有 |
| 工作窃取 deque、EBR、无锁队列、并发跳表 | 没有 | 仍然没有 |
AtomicCell<T> | 只有固定宽度的整数和指针原子类型 | 同左;AtomicU128 仍不稳定 |
CachePadded | 没有 | 没有公开类型;std 的 mpmc 内部有一份删减版(CachePadded) |
std 一侧有两处源码注释写明了来历。library/std/src/sync/mpsc.rs:142:
// MPSC channels are built as a wrapper around MPMC channels, which
// were ported from the `crossbeam-channel` crate. MPMC channels are
// not exposed publicly, but if you are curious about the implementation,
// that's where everything is.
library/std/src/sync/mpmc/mod.rs:113 则直接附上了 crossbeam 的版权和 MIT 许可全文。移植时删掉了什么、改了什么,是 和 std::sync::mpmc 对照 一章的内容。
反方向也有:crossbeam-utils 的 MSRV 是 1.60,用不了 1.70 才稳定的 std::sync::OnceLock,于是 utils 和 epoch 各带了一份照着 std 当时的实现抄的 sync/once_lock.rs(两份除开头注释外完全相同,开头注明「Based on unstable std::sync::OnceLock」),epoch 的默认收集器和 ShardedLock 的线程编号表都靠它。
现在为什么还要用它
std 吸收了「作用域线程」和「channel 的基本收发」,剩下的那些,标准库没有给出替代:
- deque:rayon 的调度器建在它上面(一个任务)。
- epoch:写无锁数据结构时「摘下的节点什么时候能 free」的通用答案(一块内存的回收);deque 扩容、skiplist 删节点都靠它。
select!和定时 channel:std 的 channel 只能一次等一个。ArrayQueue/SegQueue:不需要阻塞、只要一个无锁的 MPMC 队列时,比 channel 轻。AtomicCell:对任意T给出同一个原子 API。
crossbeam-channel 和 std channel 都能用的场景里怎么选,要看吞吐、select 和 async 需求,和 tokio mpsc、flume 对照 给了一张表和本机的测量。
本站前几卷里谁在用
| 卷 | 用了什么 | 用来做什么 |
|---|---|---|
| rayon(下一卷) | crossbeam-deque、crossbeam-utils | 每个工作线程一个 Worker、全局 Injector;sleep 模块用 CachePadded |
| tantivy | crossbeam_channel::bounded(10_000) | IndexWriter 把文档批送给索引线程 |
| tantivy | crossbeam_channel::tick | warmer 的 GC 线程每隔一段时间醒一次(它的退出问题见 warmer 线程泄漏) |
| pingora | ArrayQueue | 连接池每个节点 16 格的「热队列」 |
| pingora | SegQueue、SkipMap | TinyUFO 缓存的两条队列;new_compact 用跳表 |
| sqlx | ArrayQueue | 连接池的空闲连接队列 |
| tauri | crossbeam_channel::unbounded | tao 的 EventLoopProxy 把事件送回主线程 |
| tokio | 没有依赖,抄了数值 | 任务 Cell 的缓存行对齐取值注明抄自 crossbeam 的 cache_padded.rs |
本站框架自己不直接依赖 crossbeam,上面这些都是间接带进来的,10 在本站中 逐条列出依赖链和锁定的版本。
小结
- crossbeam 是一组可以单独依赖的并发积木(utils、epoch、deque、channel、queue,外加实验性的 skiplist),不是运行时。
- 作用域线程(1.63.0)和 channel 的基本收发(1.67.0 起
std::sync::mpsc就是 crossbeam-channel 的移植)已经进了 std;std::sync::mpmc和oneshot仍是 unstable。 - 仍只在 crossbeam 里的:deque、epoch、
select!和定时 channel、ArrayQueue/SegQueue、AtomicCell、skiplist。 - rayon、tantivy、pingora、sqlx、tao 都在用它;tokio 没有依赖它,但抄了
CachePadded的取值。