<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Formal-Verification - 标签 - 星河拾贝录</title><link>https://blog.liubang.cc/tags/formal-verification/</link><description>Formal-Verification - 标签 - 星河拾贝录</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>it.liubang@gmail.com (liubang)</managingEditor><webMaster>it.liubang@gmail.com (liubang)</webMaster><copyright>Copyright © 2019-2026 LiuBang. All Rights Reserved.</copyright><lastBuildDate>Sat, 27 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.liubang.cc/tags/formal-verification/" rel="self" type="application/rss+xml"/><item><title>形式化验证与 TLA+：行为的数学建模</title><link>https://blog.liubang.cc/posts/system/2026-06-27-%E5%BD%A2%E5%BC%8F%E5%8C%96%E9%AA%8C%E8%AF%81%E4%B8%8Etlaplus/</link><pubDate>Sat, 27 Jun 2026 00:00:00 +0000</pubDate><author><name>liubang</name></author><guid>https://blog.liubang.cc/posts/system/2026-06-27-%E5%BD%A2%E5%BC%8F%E5%8C%96%E9%AA%8C%E8%AF%81%E4%B8%8Etlaplus/</guid><description><![CDATA[<blockquote>
  <p>2015 年 9 月，AWS 的工程师在一篇广为流传的论文中写道：「TLA+ 帮助我们在 DynamoDB 的复制协议中发现了一处极端情况下的 bug——这个 bug 在任何正常测试中都不可能触发，但在足够大的规模下，它一定会发生。」五年后，这篇论文的作者 Chris Newcombe 在 re:Invent 上补充了一个细节：那个 bug 的触发条件需要 7 个事件以特定顺序交错，测试永远跑不出这个组合。</p>

</blockquote><p>本文讨论的问题只有一个：<strong>怎样在代码落地之前，用数学方法证明一个系统设计没有逻辑错误。</strong></p>
<hr>
<h2 id="一测试的边界" class="headerLink">
    <a href="#%e4%b8%80%e6%b5%8b%e8%af%95%e7%9a%84%e8%be%b9%e7%95%8c" class="header-mark"></a>一、测试的边界</h2><p>先看一个简单的问题：一个全局计数器 <code>counter</code>，两个线程各自执行 100 次递增。最终 <code>counter</code> 的值是多少？</p>
<p>学过并发编程的人知道答案：不一定是 200。<code>counter += 1</code> 不是原子操作——它包含读、加、写三步。两个线程同时读到同一个值，各自加 1 后写回，结果相当于丢失了一次递增。</p>
<p>这是并发编程中最基础的竞态条件。常规对策是加锁、用原子指令、或者按并发模型重新设计。</p>
<p>但这里有一个更根本的问题：<strong>你怎么确认你的对策是正确的？</strong></p>
<p>测试可以让你更自信，但无法穷举。两个线程各执行 100 步——不考虑更复杂的交错，仅指令级的交错组合就已经是天文数字。线程数更多、步骤稍复杂之后，状态空间急剧膨胀，任何基于采样的验证手段都碰不到边界。</p>
<p>这不是测试方法论的问题，也不是工程师不够认真。这是一个组合数学问题：</p>
<blockquote>
  <p>系统的状态空间随并发组件的数量呈指数增长——这叫状态空间爆炸（state space explosion）。</p>

</blockquote><p>2014 年，MongoDB 3.0 引入了一套基于 Raft 的复制协议。核心设计经过了代码审查、单元测试、集成测试和长达数月的 QA。但上线后在特定网络分区场景下，协议出现了脑裂——两个节点同时认为自己是 Primary。事后复盘发现，触发条件需要 4 个事件按照一种极为罕见的顺序发生，在测试环境中从未出现过。</p>
<p>另一个更著名的案例是 <strong>Therac-25 放射治疗机（1985-1987）</strong>：软件中的竞态条件导致 6 名患者接受了超过百倍剂量的辐射，其中 3 人死亡。事后调查发现，触发条件依赖于操作员在特定时间窗口内按下特定按键组合——在测试期间从未被复现。</p>
<p>这些事故有一个共同特征：<strong>不是代码写错了，是设计本身存在逻辑漏洞。</strong>代码忠实地执行了设计，但设计没有覆盖所有可能的状态交错。</p>
<p>形式化验证要解决的就是这个问题。</p>
<hr>
<h2 id="二形式化验证用数学描述行为" class="headerLink">
    <a href="#%e4%ba%8c%e5%bd%a2%e5%bc%8f%e5%8c%96%e9%aa%8c%e8%af%81%e7%94%a8%e6%95%b0%e5%ad%a6%e6%8f%8f%e8%bf%b0%e8%a1%8c%e4%b8%ba" class="header-mark"></a>二、形式化验证：用数学描述行为</h2><h3 id="21-定义" class="headerLink">
    <a href="#21-%e5%ae%9a%e4%b9%89" class="header-mark"></a>2.1 定义</h3><p><strong>形式化验证</strong>（formal verification）是指用数学语言严格描述一个系统的行为规范（specification），然后通过数学证明或穷举搜索，验证系统的行为是否满足规范。</p>
<p>拆成两步理解：</p>
<ol>
<li><strong>写规范（specification）</strong>：用精确的数学语言定义「什么行为是合法的」。</li>
<li><strong>做验证（verification）</strong>：检查系统的所有可能执行路径是否都满足这个规范。</li>
</ol>
<p>这与测试有本质区别。测试只能检查有限个具体执行路径。形式化验证检查所有路径。</p>
<h3 id="22-安全性与活性" class="headerLink">
    <a href="#22-%e5%ae%89%e5%85%a8%e6%80%a7%e4%b8%8e%e6%b4%bb%e6%80%a7" class="header-mark"></a>2.2 安全性与活性</h3><p>形式化验证中，属性分为两类。这个分类来自 Leslie Lamport 在 1977 年的一篇论文。</p>]]></description></item></channel></rss>