运行时与调度
这一部分回答一个问题: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"]
六章:
- model 与 Builder:10 个字段、8 个环境变量与解析失败的 panic;
check的主循环;max_permutations/max_duration只在整 checkpoint 间隔处检查(E4);model比check多一个日志订阅者;线程数上限(E12)。 - 协程调度器:每个模型线程是一个 generator 有栈协程,每轮重建;
STATE只在tick期间存在,model 外用 loom 类型就在这里 panic;switch外面的 async 包装;spawn 不是调度点;栈大小与弃用 API。 - Execution 与对象仓库:
Execution的字段、step复用容器;所有 loom 对象都是object::Store里的一个下标(loom 原子 8 字节、不能const构造);Operation与Action;branch_acquire等四个包装。 - Path:路径与深度优先:
Schedule、Load、Spurious三种分支;重放与「Is the model fully deterministic?」;step的回溯;分支数上限;checkpoint 的 JSON。 - DPOR:
schedule()逐段读;dpor_vv与causality两套时钟;各类对象的依赖规则;一次回溯在消息传递模型里长什么样;实测哪些操作对会增加交错。 - 抢占上限与剪枝:抢占怎么数、上限在哪里拦截、额外的回溯点;E2 的交错数与耗时;
explore/stop_exploring/skip_branch(E17)。
建议按顺序读。只想知道 LOOM_MAX_PREEMPTIONS 设多少的,可以直接读第 6 章,遇到不认识的字段再往回翻。
不重讲的部分
- loom 是什么、怎么接进一个库:crossbeam 卷:怎么验证并发代码、tokio 卷:源码地图 最后一节。本卷的 cfg(loom) 切换层 只补它们没讲的。
generatorcrate 的协程实现(上下文切换的汇编、栈分配):只讲 loom 怎么用它。- 向量时钟在内存模型里的用法(
causality、released):向量时钟与同步点。
文档与代码的出入
| 文档的说法 | 代码 | 在哪章 |
|---|---|---|
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_HISTORY | Execution 与对象仓库 |
演进
只列 git log 可查的事实。
| 时间 | 提交 | 变化 |
|---|---|---|
| 2018-12-15 | 2b37a35「Initial commit」 | schedule() 开头的「Implementation of the DPOR algorithm.」注释、Path、Execution 从第一个提交就在;调度器有四种后端 |
| 2019-07-23 | 399c071「Only use generator for scheduler (#23)」 | 只留 generator 协程 |
| 2019-07-29 | b9d5a95「Add thread preemption bounding (#28)」 | preemption_bound、LOOM_MAX_PREEMPTIONS、额外回溯点 |
| 2019-11-16 | 546c77d「fixes Loom when using async/await (#102)」 | switch 包进 async 块 |
| 2020-03-23 | 88e4225「Overhaul internals to support faster execution (#111)」 | 内部重写:object::Store、MAX_ATOMIC_HISTORY = 7、Execution::Id 从 46 413 762 起等都来自这个提交 |
| 2021-12-04 | 9a546f6「fix double panic when exceeding the branch limit in Drop」(#245,0.5.5) | assert_path_len! 在 panic 期间跳过 |
| 2023-06-11 | ce8a232「Support setting model thread stack size (#311)」(0.6.0) | thread::Builder::stack_size |
| 2023-06-26 | a4341d0「Increase max threads to 5 (#314)」(0.6.0) | MAX_THREADS 4 → 5 |
| 2023-08-04 | 8075e03「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。交错数是确定的,任何负载下都可重复;耗时注明负载。
| # | 目录 | 看什么 | 章 |
|---|---|---|---|
| E2 | exp-loom/runtime/tests/bound.rs | 上限 0–4 与不设时的交错数、耗时 | 抢占上限与剪枝 |
| E4 | exp-loom/runtime/tests/builder.rs | max_permutations / max_duration 的检查粒度 | model 与 Builder |
| E12 | 同上 | 线程数上限的报错 | 同上 |
| E17 | exp-loom/runtime/tests/explore.rs | stop_exploring / skip_branch 的效果 | 抢占上限与剪枝 |
| — | exp-loom/runtime/tests/dep.rs | 哪些操作对会增加交错数 | DPOR |
| — | exp-loom/runtime/tests/sizes.rs、nondet.rs | loom 类型的 size_of;不确定模型的 panic | Execution 与对象仓库、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 个)。
💬 关联讨论
发起讨论加载中...