Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1. 模型检查与 TLA+: 穷举状态空间找反例

TL;DR

模型检查(Model Checking):把系统的所有状态穷举出来,自动验证"不变量是否永远成立"、"会不会死锁/活锁"。TLA+(Temporal Logic of Actions,Leslie Lamport 1994)是专为并发/分布式系统设计的规格语言——Raft 作者 Ongaro 用 TLA+ 验证了 Raft,Paxos、BFT、各种共识协议都有 TLA+ 规格。这一章教你理解 TLA+ 的思维、写最小规格、跑 TLC 模型检查。

读完应能:

  1. 说清模型检查 vs 测试 vs 定理证明的区别。
  2. 看懂 TLA+ 规格的核心(状态变量 / 初始谓词 / 动作 / 不变量 / 时间性质)。
  3. 写一个简单系统的 TLA+ 规格(如互斥锁、时钟同步)并跑 TLC。
  4. 用 Lamport 的三个经典性质(安全性 / 活性 / 公平性)分析系统。
  5. 知道 TLA+ 在工业(Raft/Paxos/Amazon)里的实际用法。

一、模型检查 vs 测试

1.1 为什么测试不够

并发系统的状态空间是无穷/指数级的:

2 个进程, 每进程 10 个内部状态 → 100 个组合状态
3 个进程 → 1000
n 个进程 + 消息延迟 + 故障 → 天文数字

测试只能抽样;模型检查穷举(在可管理的小规模上)。

1.2 三者的定位

测试模型检查定理证明
怎么工作跑真实代码穷举状态空间逻辑推导
找什么bug反例(违反性质的路径)全部性质的证明
规模真实简化(小 n)任意
自动化低(人机交互)
结果"这次过""有/没有反例""证明了"
适合日常并发/分布式协议关键安全实现

note

模型检查不能证明"大系统"(状态爆炸),但它能在小规模上穷举——而并发 bug 通常在小规模就能复现。Raft 的 TLA+ 用 3-5 节点验证,抓到了活锁。


二、TLA+ 的核心思维

2.1 三个概念

状态变量 (state variables): 系统的"内存" (如 pc, x, msg)
初始状态 (Init):            系统一开始长什么样
动作 (Next):                状态如何转换 (行为 = 状态序列)

2.2 一个系统的 TLA+ 规格

---- MODULE SimpleCounter ----
EXTENDS Naturals

VARIABLE n

Init == n = 0

Next == n' = n + 1        \* 每次加 1 (n' 表示"下一个状态")

Spec == Init /\ [][Next]_n    \* 从 Init 开始, 每步执行 Next (或不变)
====
  • n' = n + 1:动作谓词,描述"下一个状态 n' 是什么"。
  • [][Next]_n:每步要么执行 Next,要么保持不变(stuttering)。

2.3 不变量(Invariant)

不变量 = 系统任何状态下都必须为真的性质。

Inv == n >= 0      \* 计数器永不为负

\* 在 .cfg 里让 TLC 检查: INVARIANT Inv

模型检查器会穷举所有可达状态,验证不变量是否一直成立;不成立就给出一条反例路径


三、实战:互斥锁的 TLA+

3.1 规格

两个进程想进临界区,必须互斥(不能同时在里面)。

---- MODULE Mutex ----
EXTENDS Naturals

CONSTANT N                      \* 进程数
VARIABLES pc                     \* 每个进程的程序计数器

Proc(i) == i \in 1..N

Init == pc = [i \in 1..N |-> "idle"]

\* 请求进入
Req(i) == pc[i] = "idle" /\ pc' = [pc EXCEPT ![i] = "waiting"]

\* 尝试获取锁: 只有当没有其他进程在临界区时
Get(i) == pc[i] = "waiting" /\ \A j \in 1..N \ {i} : pc[j] /= "crit"
           /\ pc' = [pc EXCEPT ![i] = "crit"]

\* 释放
Rel(i) == pc[i] = "crit" /\ pc' = [pc EXCEPT ![i] = "idle"]

Next == \E i \in 1..N : Req(i) \/ Get(i) \/ Rel(i)
Spec == Init /\ [][Next]_pc
====

\* === Mutex.cfg ===
\* SPECIFICATION Spec
\* CONSTANT N = 2
\* INVARIANT MutexInv

3.2 互斥性质

MutexInv == \A i, j \in 1..N : i /= j => ~(pc[i] = "crit" /\ pc[j] = "crit")
\* 任意两个进程不可能同时在临界区

TLC 跑完:验证通过(或给出反例——比如 Get 条件漏了,两个进程同时进临界区)。

3.3 时间性质(Temporal Properties)

性质含义TLA+
Safety(安全性)"坏事永不发生"不变量 / []P(永远 P)
Liveness(活性)"好事终究发生"<>P(最终 P)
Fairness(公平性)请求的资源终将被授予强/弱公平假设
[]P      : 永远 P (always P)          — 安全性
<>P      : 最终 P (eventually P)       — 活性
[]<>P    : 无限次 P                    — 重复活性
<>[]P    : 最终永远 P                  — 稳定

例:互斥锁的活性 <> (pc[i] = "crit")——进程 i 最终能进临界区(不被饿死)。

note

安全性 vs 活性是理解并发系统的关键二分:安全性保证"系统不产生坏结果"(不变式),活性保证"系统最终出结果"(不死锁不饿死)。两个都要验证。


四、TLC 模型检查器

4.1 跑起来

# 需要 TLA+ 工具 (TLA+ Toolbox / TLC)
tlc Mutex.cfg            # 读规格 + 配置

TLC 输出:

Model checking completed. No error has been found.
  State1: pc = [1 |-> "crit", 2 |-> "waiting"]
  ... (枚举所有可达状态)

如果违反不变量:

Invariant MutexInv is violated.
  Behavior up to this point:
  pc = [1 |-> "crit", 2 |-> "crit"]   ← 反例路径

4.2 配置 (.cfg) 文件

SPECIFICATION Spec
CONSTANT N = 3
INVARIANT MutexInv
PROPERTY Liveness            \* 检查活性
CONSTANTS N = 3, Timeout = 5

4.3 状态爆炸怎么缓解

手段说明
减小 N3-5 个进程通常够发现 bug
对称规约对称进程算一个
抽象去掉无关细节(消息内容 → 类型)
属性导向只检查关心的部分

五、真实应用:Raft 与 Paxos

5.1 Raft 的 TLA+

  • Raft 论文作者 Ongaro 在论文里提供 TLA+ 规格Raft.tla),验证了选举/日志复制/安全性。
  • 规格把节点建模为状态机:Follower/Candidate/Leader + 任期 + 日志。
  • 验证的性质:Election Safety(同一任期只有一个 leader)、Log Matching(日志一致)、Leader Completeness(已提交日志在后续 leader 中保留)。

5.2 Lamport 的 Paxos

  • Lamport 用 TLA+ 写了 Paxos 的严格规格(Paxos.tla),在 TLA+ 文档里就是标准例子。
  • 模型检查在有限进程/值下穷举,抓"活锁"、"选值冲突"等。

5.3 Amazon 的实践

  • Amazon 用 TLA+ 分析多个分布式系统,发现了真实系统里测试没抓到的 bug,有些会导致数据不一致。
  • 价值:在写实现之前/之中写规格,抓设计层的并发错误,而不是等线上事故。

warning

TLA+ 不是"验证实现",是"验证设计/算法"。它证明的是规格的性质,不是代码正确。要连到代码,需要后面程序验证那一层的工具(模型检查代码 / 定理证明实现)。


六、其他模型检查工具

工具语言用途
TLA+ / TLCTLA+并发/分布式协议设计验证
SpinPromela通信协议 / 并发系统模型检查
NuSMV / nuXmvSMV符号模型检查(时序逻辑 CTL)
CBMC / KLEEC/LLVM程序级模型检查 / 符号执行(见程序验证章)
AlloyAlloy关系/图结构的模型分析

七、结束 + 速查表

tip

一页快速唤回:

  • 模型检查:穷举状态空间,验证不变量/性质,找反例。适合并发/分布式协议
  • 测试 ≠ 模型检查:测试抽样、模型检查穷举(小规模)。
  • TLA+ 三件套:Init(初始)、Next(动作)、不变量。
  • 安全性 []P:坏事永不发生;活性 <>P:好事终究发生。
  • 互斥锁验证:不变量"无两进程同时在临界区",活性"最终能进"。
  • Raft/Paxos 有官方 TLA+ 规格;Amazon 用 TLA+ 抓真实并发 bug。
  • TLA+ 验证设计不验证实现——连到代码要程序验证那层。
  • 状态爆炸:减 N、对称规约、抽象。

下一篇: 2. 定理证明: Coq / Lean / 依赖类型 / Curry-Howard.