源码地图
主线位置:四条主线经过的文件。主线 1(一次 model 的一生)在
model.rs和rt/的scheduler.rs、execution.rs、path.rs;主线 2(一次 load 的候选)在rt/atomic.rs、rt/synchronize.rs;主线 3(数据竞争的检测)在rt/cell.rs和rt/atomic.rs的后半;主线 4(看到什么、看不到什么)是全卷的横线。
flowchart TB
user["被测代码(cfg(loom) 切换后)"] --> api
subgraph api["对外 mock 层:sync / cell / thread / alloc / lazy_static / future / hint(3 539 行)"]
sync["sync::atomic::AtomicUsize、Mutex、Arc、mpsc…"]
cell["cell::UnsafeCell、Cell"]
thr["thread::spawn、park、thread_local!"]
end
api -->|"rt::Atomic::new(v, location!())"| rt
subgraph rt["运行时 rt/:全部 pub(crate)(4 965 行)"]
sched["scheduler.rs:协程"]
exec["execution.rs:Execution、schedule(DPOR)"]
path["path.rs:Path"]
obj["object.rs:Store、Ref"]
prims["atomic / cell / mutex / rwlock / condvar / notify / arc / mpsc / alloc / lazy_static / thread"]
end
model["model.rs:model、Builder(278 行)"] --> rt
rt --> gen["generator(有栈协程)"]
rt --> stls["scoped-tls(STATE)"]
model --> trc["tracing + tracing-subscriber"]
两层
src/ 下共 8 504 行(含文档注释;loom 自己的源码里没有 #[cfg(test)] 模块,测试都在 tests/):
| 部分 | 行数 | 可见性 | 管什么 |
|---|---|---|---|
顶层:lib.rs、model.rs、thread.rs、alloc.rs、lazy_static.rs、hint.rs | 1 295 | pub | crate 文档(lib.rs 474 行里 344 行是文档)、model / Builder、thread 与 alloc 的 mock、两个宏 |
sync/ | 1 436 | pub | std sync 的 mock:原子、Arc、Mutex、RwLock、Condvar、mpsc,以及 loom 独有的 Notify |
cell/ | 663 | pub | UnsafeCell(带 with / with_mut / get / get_mut)与 Cell |
future/ | 145 | pub,futures feature | block_on、AtomicWaker |
rt/ | 4 965 | 全部 pub(crate)(除了 yield_now、explore 等几个转出去的函数和 MAX_THREADS) | 运行时:调度、路径、对象仓库、每种原语的状态与检查 |
对外类型几乎都只是「一个 rt 对象 + location!()」的薄壳。以 AtomicUsize::load 为例,它调 sync::atomic::Atomic::load,后者调 self.state.load(location!(), order),再往下就进了 rt::Atomic。location!() 宏在 LOOM_LOCATION 关闭时返回一个空位置,打开时借 #[track_caller] 记下调用者的文件、行、列(死锁、分支上限与位置信息)。
顶层与 sync、cell、future:每个文件一句话
| 文件 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
lib.rs | 474 | crate 文档(背景、写测试、调试、局限、实现);导出;thread_local! 与 lazy_static! 两个宏 | loom 解决什么问题 |
model.rs | 278 | Builder 10 个字段与环境变量、check 主循环、model、checkpoint 读写 | model 与 Builder |
thread.rs | 314 | spawn(无 Send 约束)、JoinHandle、Builder(名字、栈大小)、park / unpark、current、LocalKey | 线程、线程局部与 lazy_static |
alloc.rs | 109 | alloc / alloc_zeroed / dealloc 的泄漏跟踪版、Track<T> | 泄漏检测 |
lazy_static.rs | 92 | Lazy<T>:每轮重新初始化的 lazy_static;文件开头还重新导出了 AccessError、yield_now、panicking,和 thread 模块的导出相同 | 线程、线程局部与 lazy_static |
hint.rs | 28 | spin_loop(等于 yield_now)、unreachable_unchecked(直接 panic) | 死锁、分支上限与位置信息 |
sync/atomic/(5 个文件) | 607 | AtomicBool、AtomicPtr、AtomicI8…AtomicUsize 共 10 个整数类型;fence;spin_loop_hint | 03 内存模型 |
sync/arc.rs | 282 | Arc<T>:std Arc 存数据 + rt::Arc 计数;from_std | Arc、mpsc 与 future |
sync/rwlock.rs、mutex.rs、condvar.rs | 169、120、79 | 数据放在 std 的同名锁里,loom 的 rt 对象管调度与同步 | Mutex 与 RwLock、Condvar、Notify 与 park |
sync/mpsc.rs | 89 | 无界 channel;recv_timeout 是 unimplemented! | Arc、mpsc 与 future |
sync/notify.rs | 51 | loom 独有的 Notify:一次性的 park / unpark 配对,可伪唤醒一次 | Condvar、Notify 与 park |
sync/barrier.rs | 19 | Barrier 桩:new 和 wait 都是 unimplemented!("std::sync::Barrier is not supported yet in Loom.") | 本页 |
sync/mod.rs | 20 | 导出;LockResult、TryLockResult 直接用 std 的 | — |
cell/unsafe_cell.rs | 538 | UnsafeCell<T>、ConstPtr、MutPtr;大部分是文档 | UnsafeCell 与原子的非同步访问 |
cell/cell.rs | 117 | Cell<T>:在 UnsafeCell 上实现的 std Cell 接口 | 同上 |
future/mod.rs、atomic_waker.rs | 78、67 | block_on、AtomicWaker(tokio 同名类型的 mock) | Arc、mpsc 与 future |
rt:每个文件一句话
| 文件 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
mod.rs | 222 | MAX_THREADS = 5、MAX_ATOMIC_HISTORY = 7;spawn、park、branch、synchronize、yield_now、thread_done;explore / stop_exploring / skip_branch | Execution 与对象仓库 |
scheduler.rs | 161 | generator 协程、STATE、switch、run、tick | 协程调度器 |
execution.rs | 286 | Execution、new_thread、step、schedule(DPOR、选线程、死锁断言) | Execution 与对象仓库、DPOR |
path.rs | 547 | Path、Schedule / Load / Spurious、step、backtrack、抢占计数 | Path、抢占上限与剪枝 |
object.rs | 465 | Store、Ref、Entry、Operation、Action;branch_*;泄漏检查的入口 | Execution 与对象仓库 |
access.rs | 42 | Access { path_id, dpor_vv } | DPOR |
vv.rs | 86 | VersionVec:[u16; 5] 的向量时钟 | 向量时钟与同步点 |
synchronize.rs | 63 | Synchronize:按内存序做 acquire / release | 同上 |
thread.rs | 492 | 线程状态、三套时钟、Set、seq_cst / seq_cst_fence、线程局部 | 同上 |
atomic.rs | 931 | 原子的状态:7 次写的历史、coherence、match_load_to_stores、RMW、fence、非同步访问检查 | 03 内存模型、UnsafeCell 与原子的非同步访问 |
num.rs | 52 | Numeric:所有原子值都换成 u64 存 | 写历史与 coherence |
cell.rs | 188 | 单元格的读写时钟与竞争检查 | UnsafeCell 与原子的非同步访问 |
location.rs | 150 | Location、LocationSet、PanicBuilder(报错的格式) | 死锁、分支上限与位置信息 |
alloc.rs | 95 | 分配的泄漏跟踪 | 泄漏检测 |
arc.rs | 210 | Arc 的计数、三种动作与依赖、泄漏检查 | Arc、mpsc 与 future |
mutex.rs、rwlock.rs、condvar.rs、notify.rs | 155、271、104、148 | 各原语的状态、阻塞与唤醒、同步 | 05 同步原语的模型 |
mpsc.rs | 178 | channel:每条消息一个 Synchronize | Arc、mpsc 与 future |
lazy_static.rs | 87 | 每轮的 lazy_static 值表 | 线程、线程局部与 lazy_static |
futures.rs | 32 | 不参与编译:rt/mod.rs 没有声明这个模块。内容是第一个提交(2b37a35)就有的一个 Notify,引用的 _futures::executor、rt::ThreadHandle 早已不存在;最后一次被改是 88e4225(#111,2020-03) | — |
feature 与依赖
| 依赖 | 版本要求 | 可选? | 用在哪 |
|---|---|---|---|
cfg-if | 1.0.0 | 否 | if_futures! 宏 |
scoped-tls | 1.0.0 | 否 | scheduler.rs 的 STATE |
generator | 0.8.1 | 否 | 有栈协程;本卷实验锁到 0.8.11 |
tracing | 0.1.27,default-features = false, features = ["std"] | 否 | 运行时到处的 trace! / info!、iter 与 thread span |
tracing-subscriber | 0.3.8,features = ["env-filter"] | 否 | model() 每次调用都新建一个 fmt 订阅者,EnvFilter 读 LOOM_LOG |
serde(derive)、serde_json | 1.0.92、1.0.33 | 是,checkpoint feature | 把 Path 存成 JSON |
pin-utils | 0.1.0 | 是,futures feature | block_on 里的 pin_mut! |
两个 feature 都默认关闭:
| feature | 打开什么 | 不开时 |
|---|---|---|
checkpoint | Path 等类型 derive(Serialize, Deserialize);LOOM_CHECKPOINT_FILE 可用 | 设了 LOOM_CHECKPOINT_FILE 就 panic「not compiled with checkpoint feature」(model.rs:267-278) |
futures | loom::future 模块(block_on、AtomicWaker) | 没有这个模块 |
tracing-subscriber 不是可选依赖这一点值得记住:任何用 loom 的 crate,开发依赖树里都会多出它和它的 env-filter(regex 等),即使从不看 loom 的日志。
公开 API 一览
| 项 | 在哪 | 本卷 |
|---|---|---|
loom::model(f)、loom::model::Builder | model.rs | model 与 Builder |
loom::explore()、stop_exploring()、skip_branch() | rt/mod.rs | 抢占上限与剪枝 |
loom::MAX_THREADS | rt/mod.rs,pub const 只为文档导出 | model 与 Builder |
loom::thread_local!、loom::lazy_static! | lib.rs 的两个 #[macro_export] 宏 | 线程、线程局部与 lazy_static |
loom::sync::*、loom::sync::atomic::*、loom::sync::mpsc | sync/ | 05、03 |
loom::cell::{UnsafeCell, Cell, ConstPtr, MutPtr} | cell/ | 04 |
loom::thread::* | thread.rs | 05 |
loom::alloc::{alloc, alloc_zeroed, dealloc, Track, Layout} | alloc.rs | 04 |
loom::hint::{spin_loop, unreachable_unchecked} | hint.rs | 04 |
loom::future::{block_on, AtomicWaker} | future/,futures feature | 05 |
没有的东西
把 std 的代码换成 loom 时会撞上的缺口(在 src/ 里 grep 不到):
| std 里有 | loom 0.7.2 | 怎么办 |
|---|---|---|
std::sync::Weak | 没有 | 模型里避免 Weak,或只在 cfg(not(loom)) 下用 |
OnceLock、Once、LazyLock | 没有 | 用 loom::lazy_static!(每轮重新初始化,线程、线程局部与 lazy_static);crossbeam-epoch 就是这么替换默认收集器的 |
thread::scope | 没有 | 用 loom::sync::Arc + thread::spawn + join |
thread::sleep | 没有 | 模型里没有时间,用 yield_now 或同步原语 |
atomic::compiler_fence | 没有,sync::atomic 只导出 fence | crossbeam 用 fence 代替(crossbeam 卷) |
AtomicU128 / AtomicI128 | 没有 | — |
mpsc::sync_channel | 没有,只有无界 channel | — |
Barrier | 有类型,但调用即 panic | 不能用;用 Condvar 或 Notify 自己写 |
mpsc::Receiver::recv_timeout | 有方法,但调用即 panic | 同上 |
「有类型但调用即 panic」的两项尤其要注意:代码能编译,loom 测试一跑到那里就失败,报错是 unimplemented! 的文案。
tests:20 个文件
上游 tests/ 共 20 个文件、2 163 行。本卷只读不跑(不在上游目录里运行任何测试),在相关章引用它们作为「上游自己写下的预期」:
| 文件 | 测试数 | 测什么 | 本卷 |
|---|---|---|---|
smoke.rs | 1 | 一个故意错的计数器,loom 必须报错 | — |
atomic.rs | 7(1 个 #[ignore]) | lazy_static 里的 Arc、unsync_load 的非法用法、CAS 读旧值、fetch_add | 03、05 |
atomic_int.rs | 6(1 个 #[ignore]) | 各整数原子的方法 | 03 |
atomic_relaxed.rs | 4 | compare_and_swap 与内存序合法性 | RMW 与释放序列 |
fence.rs | 7 | fence 同步;SB+fences(Promising Semantics 论文)、RWC+syncs 与 W+RWC(SCFix 论文) | fence 与 SeqCst |
litmus.rs | 2(1 个 #[ignore]) | load buffering(注释:「Loom currently does not support load buffering」,被 ignore)、store buffering | SeqCst 与 load buffering |
spec.rs | 3(2 个 #[ignore]) | 从 cppreference 内存序页面转写的例子 | 03 |
unsafe_cell.rs | 13 | 单元格的各种竞争组合 | UnsafeCell 与原子的非同步访问 |
arc.rs | 6 | 基本用法、Drop 里的同步、泄漏检测、try_unwrap | Arc、mpsc 与 future |
deadlock.rs | 1 | 两把锁反序加锁的死锁 | 死锁、分支上限与位置信息 |
double_panic_in_drop.rs | 1 | 分支超限发生在 Drop 里时不应二次 panic(#245) | 同上 |
mutex.rs | 3 | 互斥、「建立 SeqCst」、into_inner | Mutex 与 RwLock |
rwlock.rs | 10 | 读写锁的各种组合与 try_* | 同上 |
rwlock_regression1.rs | 1 | 两个写者的回归测试(0.6.0 的 #300) | 同上 |
condvar.rs | 2 | notify_one、notify_all | Condvar、Notify 与 park |
mpsc.rs | 6 | 顺序与并行收发、多发送者、drop 接收端 | Arc、mpsc 与 future |
futures.rs | 2 | AtomicWaker、伪 poll | 同上 |
thread_api.rs | 8 | 线程 id、名字、栈大小、join、park / unpark | 线程、线程局部与 lazy_static |
thread_local.rs | 3 | thread_local! 每线程一份、嵌套 with、析构 | 同上 |
yield.rs | 1 | 带 yield_now 的自旋能跑完 | 死锁、分支上限与位置信息 |
小结
- 两层:
pub的 mock 层(3 539 行)只是rt对象 +location!()的薄壳;rt/(4 965 行)全部pub(crate),是本卷的主体。 rt/futures.rs32 行没有被rt/mod.rs声明,不参与编译,是第一个提交留下的。- 两个 feature:
checkpoint(serde 存Path)、futures(block_on与AtomicWaker);tracing-subscriber是必选依赖。 - 公开 API 里没有
Weak、OnceLock、scope、sleep、compiler_fence、128 位原子、sync_channel;Barrier与recv_timeout能编译、一调用就 panic。 tests/20 个文件,本卷只引用不运行。