Rust in Everything
基于 loom 0.7.2

运行时与调度

这一部分回答一个问题:loom 怎样在一个 OS 线程上,把同一个闭包反复跑很多遍,每遍走一条不同的线程交错。 后面的内存模型(03)、检测器(04)、各个原语(05)都建立在这里讲的四样东西上:一个调度循环、一个装全部状态的 Execution、一串记录分支选择的 Path、一个决定「哪里值得换序」的 schedule()。

flowchart TB
  model["model / Builder::check(model.rs)"] -->|每轮| run["Scheduler::run(rt/scheduler.rs):建协程、tick"]
  run --> op["loom 操作 → rt::branch"]
  op --> sched["Execution::schedule(rt/execution.rs):DPOR、选线程、死锁"]
  sched --> path["Path::branch_thread / branch_load(rt/path.rs):重放或新建分支"]
  sched -->|backtrack| path
  sched -->|换线程| sw["Scheduler::switch:yield 回 tick"]
  sw --> run
  run -->|一轮结束| step["Execution::step → Path::step:深度优先找下一条路径"]
  step --> model
  sched --> store["object::Store:每个对象的 last access"]

六章:

  1. model 与 Builder:10 个字段、8 个环境变量与解析失败的 panic;check 的主循环;max_permutations / max_duration 只在整 checkpoint 间隔处检查(E4);model 比 check 多一个日志订阅者;线程数上限(E12)。
  2. 协程调度器:每个模型线程是一个 generator 有栈协程,每轮重建;STATE 只在 tick 期间存在,model 外用 loom 类型就在这里 panic;switch 外面的 async 包装;spawn 不是调度点;栈大小与弃用 API。
  3. Execution 与对象仓库:Execution 的字段、step 复用容器;所有 loom 对象都是 object::Store 里的一个下标(loom 原子 8 字节、不能 const 构造);Operation 与 Action;branch_acquire 等四个包装。
  4. Path:路径与深度优先:Schedule、Load、Spurious 三种分支;重放与「Is the model fully deterministic?」;step 的回溯;分支数上限;checkpoint 的 JSON。
  5. DPOR:schedule() 逐段读;dpor_vv 与 causality 两套时钟;各类对象的依赖规则;一次回溯在消息传递模型里长什么样;实测哪些操作对会增加交错。
  6. 抢占上限与剪枝:抢占怎么数、上限在哪里拦截、额外的回溯点;E2 的交错数与耗时;explore / stop_exploring / skip_branch(E17)。

建议按顺序读。只想知道 LOOM_MAX_PREEMPTIONS 设多少的,可以直接读第 6 章,遇到不认识的字段再往回翻。

不重讲的部分

文档与代码的出入

文档的说法代码在哪章
Builder.max_threads:「must be less than MAX_THREADS」默认值 DEFAULT_MAX_THREADS = 5 就等于 MAX_THREADS = 5;这是含主线程的总数model 与 Builder
max_permutations:「Maximum number of permutations to explore」;max_duration 同理只在 i % checkpoint_interval == 0 时检查,默认每 2 万轮一次同上
Builder.log:「Log execution output to stdout」字段在 0.7.2 里没有被读;输出由 model() 装的订阅者决定,直接调 check 时 LOOM_LOG 无效;输出走测试框架的捕获同上
thread::Builder::stack_size:「Sets the size of the stack (in bytes)」原样传给 generator 的 Gn::new_opt,后者按 usize 个数解释,64 位上实际是 8 倍协程调度器
Execution.max_history 字段只存不用,真正的上限是常量 MAX_ATOMIC_HISTORYExecution 与对象仓库

演进

只列 git log 可查的事实。

时间提交变化
2018-12-152b37a35「Initial commit」schedule() 开头的「Implementation of the DPOR algorithm.」注释、Path、Execution 从第一个提交就在;调度器有四种后端
2019-07-23399c071「Only use generator for scheduler (#23)」只留 generator 协程
2019-07-29b9d5a95「Add thread preemption bounding (#28)」preemption_bound、LOOM_MAX_PREEMPTIONS、额外回溯点
2019-11-16546c77d「fixes Loom when using async/await (#102)」switch 包进 async 块
2020-03-2388e4225「Overhaul internals to support faster execution (#111)」内部重写:object::Store、MAX_ATOMIC_HISTORY = 7、Execution::Id 从 46 413 762 起等都来自这个提交
2021-12-049a546f6「fix double panic when exceeding the branch limit in Drop」(#245,0.5.5)assert_path_len! 在 panic 期间跳过
2023-06-11ce8a232「Support setting model thread stack size (#311)」(0.6.0)thread::Builder::stack_size
2023-06-26a4341d0「Increase max threads to 5 (#314)」(0.6.0)MAX_THREADS 4 → 5
2023-08-048075e03「Enable user pruning of exploration branches (#323)」(0.7.0)explore / stop_exploring / skip_branch

v0.7.2 之后到 948c8cc 的 13 个提交没有改动本部分的任何逻辑(怎么读这一卷 的版本表)。

本部分的实验

机器 M2 Max(8P + 4E),macOS,rustc 1.99.1,loom =0.7.2,一律 RUSTFLAGS="--cfg loom" cargo test --release --offline,单独的 CARGO_TARGET_DIR。交错数是确定的,任何负载下都可重复;耗时注明负载。

#目录看什么章
E2exp-loom/runtime/tests/bound.rs上限 0–4 与不设时的交错数、耗时抢占上限与剪枝
E4exp-loom/runtime/tests/builder.rsmax_permutations / max_duration 的检查粒度model 与 Builder
E12同上线程数上限的报错同上
E17exp-loom/runtime/tests/explore.rsstop_exploring / skip_branch 的效果抢占上限与剪枝
—exp-loom/runtime/tests/dep.rs哪些操作对会增加交错数DPOR
—exp-loom/runtime/tests/sizes.rs、nondet.rsloom 类型的 size_of;不确定模型的 panicExecution 与对象仓库、Path
—exp-loom/pathdump/一份只加了打印的 loom 0.7.2 副本:每轮结束打印完整 Path,schedule 与 backtrack 各打一行DPOR、一次 model 的一生

pathdump 是从 registry 复制的 loom 0.7.2 源码,diff 只有 model.rs、rt/execution.rs、rt/atomic.rs 三处由环境变量控制的 println!,不改变任何分支或状态;交错数与原版逐一核对相同(消息传递模型都是 6 个)。

💬 关联讨论

发起讨论
加载中...