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 里也有一份。
这一部分讲九件事:
- API 与 flavor:
bounded/unbounded/bounded(0)分别落到哪个 flavor;Sender三种、Receiver六种;counter.rs怎么决定谁来释放 channel;超时操作「超时了也可能成功」。 - array:有界环形缓冲:每个槽一个 stamp,
head/tail把 lap、mark、index 打包进一个整数;判定可写、可读、已满的三个等式。 - list:无界块链表:31 槽一块,块懒分配;谁读完最后一个槽谁释放块;0.5.12 到 0.5.14 被 yank 的那段事故。
- zero:会合 channel:没有缓冲,一把锁加两个等待队列,发送方把栈上的包交给接收方。
- Context 与 Waker:阻塞的那一半。线程本地的
Context、一次 CAS 决定谁被选中、SyncWaker没人等时不加锁。 - select:
SelectHandle的 8 个方法,run_select先洗牌再逐个尝试、都不行就在同一个Context上注册全部。 - at、tick、never:不存消息、按时间造消息的三种 flavor;
tick在recv和select!下漂移不一样。 - 和 std::sync::mpmc 对照:本卷核心章之一。逐文件 diff,加上一个把 std 源码复制出来逐项二分的实验,回答「同源的两份代码,吞吐为什么差 3 到 6 倍」。
- 和 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.rs | 1534 | 公开的 Sender / Receiver、构造函数、迭代器;按 flavor 分发 |
select.rs | 1299 | Token、Operation、Selected、SelectHandle、run_select / run_ready、Select、SelectedOperation |
select_macro.rs | 1153 | select! / select_biased! 的宏规则 |
flavors/list.rs | 788 | 无界 |
flavors/array.rs | 655 | 有界 |
flavors/zero.rs | 486 | 零容量 |
waker.rs | 287 | Waker / SyncWaker |
flavors/at.rs、tick.rs、never.rs | 197 / 167 / 110 | 定时与永不就绪 |
context.rs | 178 | 线程本地 Context,park 在这里 |
counter.rs | 144 | 发送端 / 接收端引用计数 |
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 分钟负载(本机同时有别的实验在跑)。
| 编号 | 目录 | 内容 | 页面 |
|---|---|---|---|
| E6 | channel-throughput/、std-mpmc-copy/ | 各 flavor、std、flume、tokio mpsc 吞吐;把 std mpmc 源码复制出来逐项二分 | vs-std、compare |
| E7 | select-fairness/ | 三分支的 select! / select_biased! / Select::new_biased 分布 | select |
| E8 | rx-drop-discard/、rx-drop-0515/ | 最后一个接收端 drop 后,缓冲里的消息什么时候析构;0.5.17 与 0.5.15 | array、vs-std |
| E11 | tick-semantics/ | tick 慢消费者时的间隔、陈旧度、漂移;recv 与 select! 两条路径 | timers |
只想会用 channel 的读者,读 API 与 flavor、select、timers;想读懂实现的,从 array 开始按顺序读。主线 1「一条消息」的全程时序图在一条消息。