Rust in Everything
基于 crossbeam 0.8.5

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、AtomicConsumecrossbeam-utils
数据结构工作窃取双端队列 deque、有界 ArrayQueue、无界 SegQueuecrossbeam-deque、crossbeam-queue
内存管理基于纪元的回收 epochcrossbeam-epoch
线程同步MPMC channel(含 select!)、Parker、ShardedLock、WaitGroupcrossbeam-channel、crossbeam-utils
工具Backoff、CachePadded、作用域线程 scopecrossbeam-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
tantivycrossbeam_channel::bounded(10_000)IndexWriter 把文档批送给索引线程
tantivycrossbeam_channel::tickwarmer 的 GC 线程每隔一段时间醒一次(它的退出问题见 warmer 线程泄漏)
pingoraArrayQueue连接池每个节点 16 格的「热队列」
pingoraSegQueue、SkipMapTinyUFO 缓存的两条队列;new_compact 用跳表
sqlxArrayQueue连接池的空闲连接队列
tauricrossbeam_channel::unboundedtao 的 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 的取值。

💬 关联讨论

发起讨论
加载中...