Rust in Everything
基于 crossbeam-channel 0.5.17

crossbeam-channel

crossbeam-channel 是一个多生产者多消费者(MPMC)channel:Sender 和 Receiver 都能 clone,都能跨线程用,阻塞的那一方用 thread::park 睡觉。它还有一个 select!,能同时等多个 channel 上的发送和接收。src/ 下 7844 行,是本卷第二大的 crate。

Rust 1.67.0(rust-lang/rust#93563)起,std::sync::mpsc 的实现换成了它的移植版,放在 std 内部的 sync/mpmc/ 模块里。所以这一部分读的代码,大部分你机器上的 std 里也有一份。

这一部分讲九件事:

  1. API 与 flavor:bounded / unbounded / bounded(0) 分别落到哪个 flavor;Sender 三种、Receiver 六种;counter.rs 怎么决定谁来释放 channel;超时操作「超时了也可能成功」。
  2. array:有界环形缓冲:每个槽一个 stamp,head / tail 把 lap、mark、index 打包进一个整数;判定可写、可读、已满的三个等式。
  3. list:无界块链表:31 槽一块,块懒分配;谁读完最后一个槽谁释放块;0.5.12 到 0.5.14 被 yank 的那段事故。
  4. zero:会合 channel:没有缓冲,一把锁加两个等待队列,发送方把栈上的包交给接收方。
  5. Context 与 Waker:阻塞的那一半。线程本地的 Context、一次 CAS 决定谁被选中、SyncWaker 没人等时不加锁。
  6. select:SelectHandle 的 8 个方法,run_select 先洗牌再逐个尝试、都不行就在同一个 Context 上注册全部。
  7. at、tick、never:不存消息、按时间造消息的三种 flavor;tick 在 recv 和 select! 下漂移不一样。
  8. 和 std::sync::mpmc 对照:本卷核心章之一。逐文件 diff,加上一个把 std 源码复制出来逐项二分的实验,回答「同源的两份代码,吞吐为什么差 3 到 6 倍」。
  9. 和 tokio mpsc、flume 对照:对照表和本机吞吐。
flowchart TB
  api["bounded / unbounded / tick ..."] --> flav{flavor}
  flav --> arr["array:stamp 环"]
  flav --> lst["list:31 槽块链表"]
  flav --> zr["zero:Mutex + 两个 Waker"]
  flav --> tm["at / tick / never:按时间造消息"]
  arr --> blk["阻塞:Context + SyncWaker"]
  lst --> blk
  zr --> blk
  blk --> park["thread::park / unpark"]
  sel["select! / Select"] --> handle["SelectHandle trait"]
  handle --> arr
  handle --> lst
  handle --> zr
  handle --> tm
  blk --> stdcopy["std::sync::mpmc:移植,删掉 select 与定时 flavor"]

文件地图

crossbeam-channel/src/ 在 0.5.17(2920c984)的行数,含文档注释和文件内测试:

文件行数内容
channel.rs1534公开的 Sender / Receiver、构造函数、迭代器;按 flavor 分发
select.rs1299Token、Operation、Selected、SelectHandle、run_select / run_ready、Select、SelectedOperation
select_macro.rs1153select! / select_biased! 的宏规则
flavors/list.rs788无界
flavors/array.rs655有界
flavors/zero.rs486零容量
waker.rs287Waker / SyncWaker
flavors/at.rs、tick.rs、never.rs197 / 167 / 110定时与永不就绪
context.rs178线程本地 Context,park 在这里
counter.rs144发送端 / 接收端引用计数
err.rs、utils.rs、lib.rs—错误类型、洗牌与非 poison 的 Mutex、导出

它依赖 crossbeam-utils 的 Backoff 和 CachePadded(见 Backoff 和 CachePadded),tick 还用了 AtomicCell<Instant>。它不依赖 crossbeam-epoch:array 的槽是预分配的,list 的块由读者自己释放(见 list),用不着纪元回收。

实验

本部分的实验都在 exp-crossbeam/ 下,release 构建,本机 M2 Max(8 性能核 + 4 能效核),macOS 不能绑核,吞吐类数字是每个进程跑 5 次取中位数,再至少换序跑 3 轮;页面上同时写出测量时的 1 分钟负载(本机同时有别的实验在跑)。

编号目录内容页面
E6channel-throughput/、std-mpmc-copy/各 flavor、std、flume、tokio mpsc 吞吐;把 std mpmc 源码复制出来逐项二分vs-std、compare
E7select-fairness/三分支的 select! / select_biased! / Select::new_biased 分布select
E8rx-drop-discard/、rx-drop-0515/最后一个接收端 drop 后,缓冲里的消息什么时候析构;0.5.17 与 0.5.15array、vs-std
E11tick-semantics/tick 慢消费者时的间隔、陈旧度、漂移;recv 与 select! 两条路径timers

只想会用 channel 的读者,读 API 与 flavor、select、timers;想读懂实现的,从 array 开始按顺序读。主线 1「一条消息」的全程时序图在一条消息。

💬 关联讨论

发起讨论
加载中...