Rust in Everything
基于 crossbeam 0.8.5

动手

前面十章读的是 crossbeam 怎么做。这一部分反过来,用最少的代码把两个核心机制自己写一遍,再用测试和 loom 确认它们对、确认删掉某一行会错。页面只放思路和关键片段,完整代码在 exp-crossbeam/hands-on/(src/mini_channel.rs 254 行、src/mini_ebr.rs 146 行,含注释),依赖只有 crossbeam-utils =0.8.23(用它的 Backoff 和 CachePadded)和可选的 loom =0.7.2。所有片段都从这两个文件里摘出,编译、测试都跑过(本机 M2 Max、rustc 1.99.1、release)。

  1. 最小有界 channel:array flavor 的 stamp 环原样搬过来,阻塞部分用一个「每个等待者一个 claimed 标志」的列表代替 Context / SyncWaker。4 种容量 × 4 种生产者 / 消费者形状的压力测试通过;去掉「注册后复查」,测试三次都卡死。
  2. 最小 EBR:一个全局纪元、固定几个参与者槽位、三个按 epoch % 3 轮换的垃圾袋,退休和推进共用一把锁,读侧只写自己的槽位加一道 fence。loom 在两线程、三线程模型下都没发现「读者手里的节点被释放」;读者不 pin、去掉 pin 里的 fence、去掉推进里的 fence,三种改动 loom 都抓到了。
flowchart TB
  ch["最小 channel"] --> ring["stamp 环:try_send / try_recv"]
  ch --> blk["阻塞:Backoff → register → 复查 → park"]
  blk --> rev1["反向:去掉复查 → 卡死"]
  ebr["最小 EBR"] --> pin["pin:写本地纪元 + SeqCst fence"]
  ebr --> adv["try_advance:fence → 读所有本地纪元 → 推进 → 释放 e-1 袋"]
  ebr --> loom["loom:通过"]
  loom --> rev2["反向:不 pin / 去掉任一 fence → loom 报错"]

第三个:Chase-Lev 的 loom 模型

规划里的第三项「用 loom 写 Chase-Lev 的 push / pop / steal 小模型,去掉 fence 看 loom 报错」已经在 05 章做过,不在这里重复:exp-crossbeam/loom-chase-lev/ 照 0.8.8 的 LIFO pop 和 steal 抄了一个容量固定为 4 的版本,完整协议 65 个交错全部通过,去掉 pop 或 steal 任一侧的 SeqCst fence,第一个交错就取出了 [1, 2, 2](Chase-Lev 双端队列)。它和本部分的 EBR 模型是同一种 store-buffering 形状:一方写自己的变量、fence、读对方的变量,两边都要有 fence。

怎么跑

cd exp-crossbeam/hands-on
cargo test --offline --release                           # channel 压力测试、EBR 多线程测试
cargo test --offline --release --features no_recheck --test channel   # 反向:应当卡死,60 秒看门狗 abort
LOOM_MAX_PREEMPTIONS=2 cargo test --offline --release --features loom --test ebr_loom

loom 那一行建议另给一个 --target-dir:loom feature 会让 mini_ebr 换成 loom 的原子类型,和普通构建共用目录会来回重编。

和课程的关系

这两个实现刻意省掉了很多东西(channel 没有断开、超时和 select;EBR 只有固定槽位、退休要拿锁),每一页末尾列出了省掉的部分和 crossbeam 对应的做法。把它们补全是课程的内容,本卷只保证这里的骨架是对的,并且能说出每一道 fence、每一次复查防的是什么。

💬 关联讨论

发起讨论
加载中...