第一个例子
第1章 一个例子
1.1 问题域
银行,Alice和Bob是两个用户,银行提供了在线转帐的业务(wire":
- 每个转帐业务只在不同的两个银行客户之间进行,转帐金额最少一美元
- 若成功了,金额会从转出者的帐户减去,并加到转入者帐户上
- 若失败,两个账户都不受影响
- 不能让帐上金额为负
- 多个转帐可以同时进行
1.1.1 代码框架
ToolBox
1.2.2 定义规格
两件事要定义:人(帐户)的集合,以及每个帐号的金额
- 在
variables
中定义变量 - 在
define
区域中定义不变量
1 | EXTENDS Integers |
1.1.3 实现算法
1 | …… |
用plusCal写好后,可以转为TLA
在这个实现中,
sender
是转出方,我们用Withdraw
和Deposit
分别处理转出和转出帐户的处理。
1.1.4验证
配置Model,指定不变量
1.1.5 更多的初值配置
修改amount初始值模式,可以甲范围方式
amount \in 1..6;
测试会失败,因为amount=6时违反了不变量NoOverdrafts的规定
1.2 并行处理
使用process
。
把业务处理放到process里面。
还要加上对转出金额的检查
1 | EXTENDS Integers |
仍然是NoOverdrafts
错误。
可以通过error trace
来分析错误原因:
Initial Predicate
是初始状态,所有的变量都在这里被赋值。pc
表示了每个process
的位置。后面每一个状态中,发生变化的都被高亮标识出来。例如,中状态2中,执行完了第一个process的
CheckFunds
,准备执行Withdraw
process,所以pc中的第一个变成了Withdraw
。而第二个process还没有开始执行,因此它的pc标签仍然是CheckFunds
。在状态4,进程1的状态从
Withdraw
变到了Deposit
,表示做了一次withdraw操作。因此,acc[“alice”]
从5变成了4。通过阅读trace信息,我们就可以复原执行过程:- 选择
amount[1]=1, amount[2]=5
- 执行
CheckFunds[1]
. 因1<=acc[“Alice”]
, 处理到Withdraw[1]
- 执行
CheckFunds[2]
, 因5<=acc[“alice]
,执行Withdraw[2]
- 执行
Withdraw[1]
,此时acc[“Alice”]=4
- 执行
Withdraw[2]
, 此时acc[“alice]=-1
。错误。
- 选择
这是一个同步问题。因为检查帐户和划出金额之间的并发竞争冲突。
修改为把两个动作放到同一个标签中:
1 | process Wire \in 1..2 |
1.3 时间属性
检查规则若转帐失败,帐户不应变动
换用一个稍微弱一些的规则:帐户最后的总金额等于转帐前的总金额
称为Temporal Property
时间属性和不变量的区别:
- 不变量会在每步执行后检查
- 时间属性在算法的每个可能的生命周期检查
- 类似于"始终满足"和"最终满足"的差别
需要在define
中新增定义:EveentuallyConsistent
1 | define |
<>[]
的意思是"eventually-always"
在Model Overview
中添加时间属性名称
执行仍然错误:
第5步,出了错误Stuttering错误