Skip to content

模型Model

并发分离逻辑

Concurrent separation logic

以可分资源组织并发推理,并用每线程贡献token、锁不变式与作用域回收证明两个客户端精确计数2。

形式陈述 ​

并行规则与安全执行 ​

并发分离逻辑在资源上下文 Γ 下使用 Hoare 判断。经典并行规则的核心形状是

Γ⊢{P1} C1 {Q1}Γ⊢{P2} C2 {Q2}Γ⊢{P1∗P2} C1∥C2 {Q1∗Q2}.

分离合取 P1∗P2 把可组合资源拆给两个线程。每条命令只能读取或修改其断言授权的部分;普通程序变量还须满足修改集合与另一线程自由变量不冲突等侧条件。资源上下文中的共享资源只能按逻辑规定的同步规则取得。若这些条件满足,另一线程根本没有能力触碰本地拥有的堆片段,于是大批 干扰自由 义务由所有权语义统一解除。

该规则通常证明部分正确性与安全执行:若两边从分离资源出发且各自局部安全,那么任意合法交错不会因越权解引用而 fault,终止时资源仍可重新组合。精确结论依所选语言的 allocation、deallocation、并发读写和故障语义而定。

这里的“局部安全”不仅是最终后置正确。每个解引用前都要拥有相应 points-to 或权限,free 要拥有可释放资源,分配要取得 fresh 单元;因此一个线程不能先发生非法访问、再碰巧恢复正确终态。并行规则把这种逐步 fault avoidance 一并组合。

精确计数所用的有限 ghost 扩展 ​

下面在顺序一致的交错语义中验证两个客户端各进入一次互斥临界区。共享地址 x 不变,保存数学整数;读写只发生在锁内,没有异常退出、溢出或第三个使用者。resource r in C 创建局部同步资源,执行 C,在其全部子线程结束后退出作用域并归还资源不变式。不是任意共享锁都能在一次 join 后这样回收。

为记录每次贡献,引入与真实地址命名空间分离的有限 ghost 堆。其单元片段记为 γ↦gqb,其中 0<q≤1 为有理数,b∈{0,1}。使用分数权限的组合规则:同名片段必须值相同、份额之和至多为一;不同名字独立组合。因此

(γ↦gq1b)∗(γ↦gq2b)≡γ↦gq1+q2b(q1+q2≤1),

而 γ↦gq1b1∗γ↦gq2b2 蕴含 b1=b2。允许分配相对全部当前资源及 frame 都新鲜的 full ghost 单元;只有拥有份额 1 时才能把它的值改成任意新位。这些是分数 ghost 状态的标准规则,[5; 6] 此处明确作为经典堆模型的扩展使用,而不把它们冒充原始堆不交模型已有的规则。

拆分与一致性直接来自上述部分组合运算。full 更新保持任意兼容 frame:总份额已是一,frame 不可能再含该名字的正份额,所以更新不会改变别人已拥有的断言。新鲜分配同样不与 frame 冲突。ghost 分配与更新只记录证明信息,不决定可执行程序的分支、真实写值或调度行为;擦除它们不改变具体执行。

分配两个不同的新名字 γ1,γ2,定义客户端贡献凭证和锁不变式:

Ti(b)=γi↦g1/2b,I=∃b1,b2∈{0,1}.x↦(b1+b2)∗γ1↦g1/2b1∗γ2↦g1/2b2.

锁保管每个贡献的一半,客户端保管另一半。Ti(0) 表示尚未贡献,Ti(1) 表示已贡献;每个客户端只调用一次增量操作。

这里 I 在真实堆与 ghost 堆的乘积资源模型中是精确的:固定的三个位置和固定份额决定唯一候选子资源,其值由总资源决定,存在量词仅检查这些值是否符合求和关系。因而开闭及局部资源规则所需的精确性条件有具体依据。

直觉

传统并发证明盯着每个外来动作问“它会不会改坏我的断言”。并发分离逻辑先问“它是否拥有进入这块状态的门票”。独占资源只能出现在一个线程的断言中;没有门票的代码不能合法读写它,因此局部链表更新可与另一线程的独立树更新直接并行,而不用比较每对指令。

所有权是逻辑资源,不等同于机器地址只被一个变量引用。两个指针可以都保存同一地址,但若当前线程没有相应权限,仍不能解引用。相反,权限代数可以把同一位置的只读份额分给多个线程。关键不是指针数量,而是证明状态中资源怎样拆分、转移和重组。

纯变量也会造成干扰。即使堆片段分离,线程 1 的后置若依赖普通变量 z,线程 2 又能赋值 z,单靠星号不能保护该事实;经典规则的自由变量与修改集合侧条件正用于堵住这条缝。把所有共享状态都画成“堆”而省略 store 部分,会漏掉这一层。

贡献凭证不直接拥有计数器 x。它在锁外保留“本线程已经贡献多少”的事实;持锁后,与锁中的同名半份合并才允许改变该项贡献。其他线程即使拿到整个锁,也只有当前线程 ghost 名字的一半,不能独自改写它。这正是局部后置在他人运行期间保持有效的原因。

例子与边界

不相交单元与弱共享摘要 ​

初始堆满足

p↦2∗q↦5.

两个线程分别执行 [p]:=[p]+1 与 [q]:=[q]+1。局部规格为

{p↦2} C1 {p↦3},{q↦5} C2 {q↦6}.

并行规则合成

{p↦2∗q↦5}C1∥C2{p↦3∗q↦6}.

无论哪次写先发生,另一格都在独立资源片段中。若 p=q,前置中的两个完整 points-to 无法分离,公式本身不可满足;逻辑不会把同址双写伪装成安全并行。

共享写入可以通过锁资源逐步解释。设锁的不变式为 I0≡∃n.p↦n。锁空闲时,这格内存归共享资源;线程取得锁后,暂时获得 p↦n,读写后建立 p↦n+1,再隐藏新值以恢复 I0 并释放锁。另一线程只能在随后取得锁时接手资源,不能在持锁者更新中途打开同一不变式。

这个存在量词不变式只足以证明安全访问,不足以独自证明“两个线程结束后值恰好增加二”。后一个结论还要在规格中追踪初值和每次更新的贡献;不能把很弱的资源摘要误当成精确功能规格。

一个客户端的完整局部推导 ​

下面使用前述精确贡献不变式 I。用 i∈{1,2}、j≠i 统一表示客户端;ui 是其私有临时变量。带证明注解的临界区为

text
Ci = with r when true do
       ui := [x]
       [x] := ui + 1
       ghost gamma_i := 1

真实程序擦除最后一行。打开锁时,Ti(0)∗I 提供两个同名半份:客户端声称零,不变式声称 bi;组合一致性立即给出 bi=0。设另一线程贡献为 bj=b,打开后的资源可重排成

x↦b∗γi↦g10∗γj↦g1/2b.

读操作得到 ui=b,真实写操作得到 x↦(b+1),full ghost 更新得到 γi↦g11。再拆为两个半份,保留一份作为 Ti(1),另一份与未变的 γj 半份和新堆值一起组成 I,见证取 bi=1,bj=b。因此临界区内部证明

{Ti(0)∗I} 临界区体 {Ti(1)∗I}

通过资源不变式的开闭规则得到

r:I ⊢ {Ti(0)} Ci {Ti(1)}.

ui 只属于该线程;地址 x 的值不变,其他线程不修改这个地址变量。锁保证两次真实读写之间另一客户端不能进入,因此临界区内可暂时打破 x=b1+b2,但释放前必须恢复。

并行组合、回收不变式与精确的二 ​

从 x↦0 开始,在证明中分配 γ1↦g10∗γ2↦g10,各拆成两半。把一半留给相应客户端,另一半连同 x↦0 用见证 b1=b2=0 建立 I。初始化资源为

T1(0)∗T2(0)∗I.

两次局部推导由并行规则合成

r:I ⊢ {T1(0)∗T2(0)} C1∥C2 {T1(1)∗T2(1)}.

并行命令结束意味着两个客户端都结束;再退出容纳所有使用者的局部资源作用域,按规则归还 I:[2, §8.3]

{T1(0)∗T2(0)∗I} resource r in (C1∥C2) {T1(1)∗T2(1)∗I}.

打开这个最终存在量词时,两份 Ti(1) 分别与 I 中的半份一致,迫使 b1=b2=1,于是取得

x↦2∗γ1↦g11∗γ2↦g11.

忽略证明用 ghost 堆,投影回真实程序就得到所需的部分正确性规格:从 x↦0 出发,局部锁内两个客户端各增量一次,若整个命令结束,便返回 x↦2。没有把锁仍保管的内存同时算进客户端后置,也没有在未回收不变式时从 token 直接读出 x。

两种临界区先后次序可以逐行核对;表只记录每次临界区已经关闭的时刻:

顺序 初始 (b1,b2,x) 第一次关闭后 第二次关闭后
C1 再 C2 (0,0,0) (1,0,1) (1,1,2)
C2 再 C1 (0,0,0) (0,1,1) (1,1,2)

等待、取得和释放可以有更多交错;互斥保证两段实际更新仍落在上述一种次序。证明已对任意 b∈{0,1} 给出局部规则,表格不是对调度的额外假设。

凭证防止哪些错误推导 ​

第二次调用同一客户端增量规格需要 Ti(0),而第一次后只剩 Ti(1);重新取得锁时,一致性只能确认旧贡献是一,不能把它当零套用上述推导。若要允许重复调用,必须改成可累计的贡献协议,不能复用这个一次性接口。

不能把 Ti(0) 复制成两个半份 token:它本身只有半份,复制后两个客户端再加上锁的一半总量为 3/2,不可组合。允许仅凭半份更新也会破坏其他持有者的值断言。若删除真实锁,两线程可能都读零再各写一;ghost 注解不能修复这条真实的丢失更新执行。

这份证明只依赖已列出的有限资源规则、互斥临界区语义与局部作用域回收;不是对任意现代 CSL 的完整可靠性证明。一般持久不变式的消除可能需要另外的取消凭证,不能套用这里的局部资源规则。

两个线程只读同一格时,完全不交的堆模型又过于严格,需要 分数权限 或其他共享只读代数。经典 CSL 也不自动建模弱内存重排、无锁算法的原子 ghost 更新、hazard pointer 或 epoch reclamation;直接套用堆不交规则会遗漏时间和内存回收协议。

推论与应用

并发分离逻辑使库规格保持“小脚印”:函数只声明实际触及的资源,调用者的其余堆可由 frame 原则保留,并行规则再把不同线程的小脚印拼接。资源不变式支持锁与临界区,所有权转移支持消息和线程创建,现代逻辑还加入 ghost state、原子更新与协议状态机来证明线性一致性。

安全性边界必须保持清楚。一个线程先拿锁 L1 再等 L2,另一个先拿 L2 再等 L1,仍可能在任何一步都没有越权访问,却一起死锁。局部所有权因此不说明线程最终获得资源、操作无饥饿或算法满足 lock-free 进展。工具若自动完成 entailment,也只是在受支持的断言片段中搜索证明;逻辑规则的可靠性仍依赖并发操作语义与资源代数。

在弱内存模型中,即使权限排除了同址竞争,不同位置的写入仍可能以另一顺序被观察。若证明依赖 release/acquire 建立 happens-before,就要把原子模式和内存序纳入逻辑规则;经典顺序一致交错语义下的 CSL 证明不能自动承担这项硬件保证。

参考资料

[1] Peter W. O’Hearn, “Resources, Concurrency and Local Reasoning,” Theoretical Computer Science 375(1–3), 2007, pp. 271–307。

[2] Stephen Brookes, “A Semantics for Concurrent Separation Logic”, Theoretical Computer Science 375(1–3), 2007, pp. 227–270,资源上下文、临界区规则与并行组合的可靠性。

[3] John C. Reynolds, “Separation Logic: A Logic for Shared Mutable Data Structures,” LICS, 2002, pp. 55–74。

[4] Peter W. O’Hearn, “Separation Logic,” Communications of the ACM 62(2), 2019, pp. 86–95。

[5] Tej Chajed, Verifying a concurrent, crash-safe file system with sequential reasoning, MIT博士论文,2022,§3.1.2,pp. 31–33:ghost状态擦除、分数分配、拆分、一致性与full更新。

[6] Iris项目,ghost_var 正式库,ghost_var_agree、ghost_var_split、ghost_var_update与ghost_var_update_halves。这些引理支持分数ghost规则,不是本文局部锁算例已经机器验证的声明。

关系图谱12 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系