简单并发程序的归纳不变量是什么?

时间:2014-07-28 06:44:44

标签: concurrency invariants correctness tla+ tlaps

以下是Leslie Lamport撰写的文章Teaching Concurrency中的简单并发程序。

考虑从0到$ N-1 $编号的$ N $进程,其中每个进程$ i $执行

x[i] := 1
y[i] := x[(i - 1) % N]

并停止,其中每个x[i]最初等于0.(假设每个x[i]的读写都是原子的。)

此算法满足以下属性:每个进程停止后,y[i]至少等于1 一个过程$ i $。它很容易验证:写y[i]的最后一个$ i $进程必须 将其设为1.

然后,Lamport评论说

  

但是这个过程没有将y[i]设置为1,因为它是写$ y $的最后一个过程。

     

算法满足此属性,因为它保持归纳不变。你知道那个不变量是什么吗?如果没有,那么你不完全理解为什么算法满足这个属性。

因此,

  

并发程序的归纳不变量是什么?

2 个答案:

答案 0 :(得分:4)

x s模型具有以下行为:x[i]当前1i当且仅当流程x已经运行时。当然,在所有流程完成后,所有1都设置为y

y[i]有点棘手:x[i-1]设置为y[i],即1i当且仅当<当i正在写y时,y[i]的前任已经运行。

程序不变量为:如果某个流程已设置x[i],则必须已将1设置为y[i] 。无论0是设置为1还是y,都是如此。

证明这个不变量非常简单:一开始,y[i] s都没有设置,所以它很简单。在程序执行期间,每次写入x[i]sequenced after写入y[i]。因此,不变量也适用于该程序的每一步。

进一步的推理是这样的:完成的最后一个过程将1设置为y,因为根据最后一个过程的定义,它的前任必须已经在那时完成执行(即。其x值已设置。这意味着,由于不变量,其y值(确定最后一个流程&#39; 1值)必须为y

查看它的另一种方法:找不到所有0设置为y的执行顺序。这样做将要求所有进程在其前任之前执行。但是,由于我们的进程被排列成一个环(也就是说,如果我遵循前一个链,我最终会再次出现在我的起点),这会导致至少一个进程在写入之前必须完成执行的矛盾。 {{1}}。

答案 1 :(得分:4)

TL; DR

该程序的归纳不变量,用TLA+语法表示:

/\ \A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1) 
/\ (\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1

什么是归纳不变量?

归纳不变量是满足以下两个的不变量 条件:

Init => Inv
Inv /\ Next => Inv'

其中:

  • Inv是归纳不变的
  • Init是描述初始状态的谓词
  • Next是描述状态转换的谓词。

为什么要使用归纳不变量?

请注意,归纳不变量仅与当前状态和下一状态有关。它 没有引用执行历史, 它不是关于系统过去的行为。

并发系统原理和规范的第7.2.1节 (通常称为The TLA+ Hyperbook), Lamport描述了为什么他更喜欢使用归纳不变量而不是行为 证明(即那些参考执行历史的证据)。

  

行为证明可以更正式,但我不知道任何实际的方法   使它们完全正式 - 也就是说,写出可执行的描述   真实算法和形式行为证明它们满足正确性   属性。这是超过35年写作的原因之一   并发算法,我发现行为推理是不可靠的   更复杂的算法。我相信行为的另一个原因   证明本质上比基于状态的证据更加复杂   复杂的算法。这导致人们写出不那么严格的行为   这些算法的证明 - 特别是没有完全正式的证明   作为指南。

     

为了避免错误,我们必须从国家的角度思考,   不是在执行方面......仍然,行为推理提供了一个   不同的思考算法的方式,思考总是有帮助的。   只有在使用行为推理而不是基于状态的情况下,行为推理才是错误的   推理而不是除此之外。

一些预赛

我们感兴趣的属性是(在TLA +语法中):

(\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1

我在这里使用描述每个控件状态的PlusCal约定 使用名为&#34; pc&#34;的变量的过程(我认为它是&#34;程序计数器&#34;)。

此属性是不变的,但它不是归纳不变量,因为它 不满足上述条件。

您可以使用归纳不变量通过编写如下所示的证据来证明财产:

1. Init => Inv
2. Inv /\ Next => Inv'
3. Inv => DesiredProperty

要想出一个归纳不变量,我们需要为每一步给出标签 算法,让他们称呼他们&#34; s1&#34;,&#34; s2&#34;和&#34;完成&#34;,其中&#34;完成&#34;是个 每个过程的终端状态。

s1: x[self] := 1;
s2: y[self] := x[(self-1) % N]

提出归纳不变

当程序处于倒数第二个(倒数第二个)状态时,请考虑该程序的状态 执行。

在上一个执行状态中,pc[i]="Done"表示i的所有值。在里面 倒数第二个状态,pc[i]="Done"表示i的所有值,除了一个,让我们调用它 j,其中pc[j]="s2"

如果进程i在&#34;完成&#34;状态,那么x[i]=1必须是真的,因为该过程必须执行语句&#34; s1&#34;。 类似地,进程j处于状态&#34; s2&#34;也必须执行 声明&#34; s1&#34;,所以x[j]=1必须是真的。

我们可以将其表达为一种不变量,这恰好是一种归纳法 不变的。

\A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1)

PlusCal模型

为了证明我们的不变量是一个归纳不变量,我们需要一个合适的模型 Init状态谓词和Next状态谓词。

我们可以从PlusCal中描述算法开始。这很简单 算法,所以我称之为&#34;简单&#34;。

--algorithm Simple

variables
    x = [i \in 0..N-1 |->0];
    y = [i \in 0..N-1 |->0];

process Proc \in 0..N-1 
begin
    s1: x[self] := 1;
    s2: y[self] := x[(self-1) % N]
end process

end algorithm

转换为TLA +

我们可以将PlusCal模型转换为TLA +。这就是我们的样子 将PlusCal翻译成TLA +(我已经省略了终止条件,因为 我们在这里不需要它。)

------------------------------- MODULE Simple -------------------------------

EXTENDS Naturals

CONSTANTS N

VARIABLES x, y, pc

vars == << x, y, pc >>

ProcSet == (0..N-1)

Init == (* Global variables *)
        /\ x = [i \in 0..N-1 |->0]
        /\ y = [i \in 0..N-1 |->0]
        /\ pc = [self \in ProcSet |-> "s1"]

s1(self) == /\ pc[self] = "s1"
            /\ x' = [x EXCEPT ![self] = 1]
            /\ pc' = [pc EXCEPT ![self] = "s2"]
            /\ y' = y

s2(self) == /\ pc[self] = "s2"
            /\ y' = [y EXCEPT ![self] = x[(self-1) % N]]
            /\ pc' = [pc EXCEPT ![self] = "Done"]
            /\ x' = x

Proc(self) == s1(self) \/ s2(self)

Next == (\E self \in 0..N-1: Proc(self))
           \/ (* Disjunct to prevent deadlock on termination *)
              ((\A self \in ProcSet: pc[self] = "Done") /\ UNCHANGED vars)

Spec == Init /\ [][Next]_vars
=============================================================================

请注意它如何定义InitNext状态谓词。

TLA +

中的归纳不变量

我们现在可以指定我们想要证明的归纳不变量。我们也想要我们的 归纳不变意味着我们有兴趣证明的财产,所以我们 将它添加为连接。

Inv == /\ \A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1)
       /\ (\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1

非正式的handwaving&#34;证明&#34;

1。 Init => Inv

显然为什么会这样,因为如果Inv为真,Init中的前提都是假的。

2。 Inv /\ Next => Inv'

Inv&#39;的第一个结合点。
(\A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1))'

有趣的案例是某些i pc[i]="s1"pc'[i]="s2"。通过 s1的定义,应该清楚为什么这是真的。

Inv&#39;的第二个结合点。
((\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1)'

有趣的案例是我们之前讨论的案例,其中pc[i]="Done"为。{1}} i的所有值,除了一个j,其中pc[j]="s2"

通过Inv的第一个结合,我们知道x[i]=1代表i的所有值。

s2y'[j]=1

3。 Inv => DesiredProperty

在这里,我们想要的属性是

(\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1

请注意,我们只是将我们感兴趣的财产归还给了 不变,所以这是微不足道的证明。

TLAPS的正式证明

您可以使用TLA+ Proof System(TLAPS)进行编写 一个正式的证据,可以机械检查,以确定它是否正确。

这是我使用TLAPS编写和验证的证明,该TLAPS使用归纳不变量 证明所需的财产。 (注意:这是我写过的第一个证明 使用TLAPS,所以请记住这是由新手写的。)

AtLeastOneYWhenDone == (\A i \in 0..N-1 : pc[i] = "Done") => \E i \in 0..N-1 : y[i] = 1

TypeOK == /\ x \in [0..N-1 -> {0,1}]
          /\ y \in [0..N-1 -> {0,1}]
          /\ pc \in [ProcSet -> {"s1", "s2", "Done"}]

Inv == /\ TypeOK
       /\ \A i \in 0..N-1 : (pc[i] \in {"s2", "Done"} => x[i] = 1)
       /\ AtLeastOneYWhenDone

ASSUME NIsInNat == N \in Nat \ {0}       

\* TLAPS doesn't know this property of modulus operator
AXIOM ModInRange == \A i \in 0..N-1: (i-1) % N \in 0..N-1


THEOREM Spec=>[]AtLeastOneYWhenDone
<1> USE DEF ProcSet, Inv
<1>1. Init => Inv
    BY NIsInNat DEF Init, Inv, TypeOK, AtLeastOneYWhenDone
<1>2. Inv /\ [Next]_vars => Inv'
  <2> SUFFICES ASSUME Inv,
                      [Next]_vars
               PROVE  Inv'
    OBVIOUS
  <2>1. CASE Next
    <3>1. CASE \E self \in 0..N-1: Proc(self)
      <4> SUFFICES ASSUME NEW self \in 0..N-1,
                          Proc(self)
                   PROVE  Inv'
        BY <3>1 
      <4>1. CASE s1(self)
        BY <4>1, NIsInNat DEF s1, TypeOK, AtLeastOneYWhenDone
      <4>2. CASE s2(self)
        BY <4>2, NIsInNat, ModInRange DEF s2, TypeOK, AtLeastOneYWhenDone
      <4>3. QED
        BY <3>1, <4>1, <4>2 DEF Proc
    <3>2. CASE (\A self \in ProcSet: pc[self] = "Done") /\ UNCHANGED vars
        BY <3>2 DEF TypeOK, vars, AtLeastOneYWhenDone
    <3>3. QED
      BY <2>1, <3>1, <3>2 DEF Next
  <2>2. CASE UNCHANGED vars
    BY <2>2 DEF TypeOK, vars, AtLeastOneYWhenDone
  <2>3. QED
    BY <2>1, <2>2
<1>3. Inv => AtLeastOneYWhenDone
    OBVIOUS
<1>4. QED
    BY <1>1, <1>2, <1>3, PTL DEF Spec 

请注意,在使用TLAPS的证明中,您需要有一个类型检查不变量(它上面称为TypeOK),您还需要处理&#34;口吃状态&#34;其中没有变量发生变化,这就是为什么我们使用[Next]_vars代替Next

Here's a gist完整的模型和证明。