我试图弄清楚SPIN如何在以下示例中选择执行和终止进程的顺序。我意识到SPIN的主要焦点是分析并发进程,但出于我的目的,我只对简单的线性执行感兴趣。在下面的例子中,我只想要step1()然后step2()按顺序执行。
int globA;
int globB;
proctype step1() {
atomic {
globA = 1;
}
}
proctype step2() {
atomic {
globB = 2;
}
}
init {
atomic {
run step1();
run step2();
}
}
但是,即使使用atomic {}声明,进程也会在执行时交错。使用命令spin -p my_pml_code.pml
我只得到以下3个输出(我跑了很多次,这是唯一的输出)。
运行1:
0: proc - (:root:) creates proc 0 (:init:)
Starting step1 with pid 1
1: proc 0 (:init::1) creates proc 1 (step1)
1: proc 0 (:init::1) pml_testing/transition_testing.pml:16 (state 1) [(run step1())]
Starting step2 with pid 2
2: proc 0 (:init::1) creates proc 2 (step2)
2: proc 0 (:init::1) pml_testing/transition_testing.pml:17 (state 2) [(run step2())]
3: proc 2 (step2:1) pml_testing/transition_testing.pml:11 (state 1) [globB = 2]
4: proc 1 (step1:1) pml_testing/transition_testing.pml:6 (state 1) [globA = 1]
4: proc 2 (step2:1) terminates
4: proc 1 (step1:1) terminates
4: proc 0 (:init::1) terminates
3 processes created
订单是proc 0 - > 0 - > 0 - > 0 - > 2 - > 1 - > 2 - > 1 - > 0
运行2:
0: proc - (:root:) creates proc 0 (:init:)
Starting step1 with pid 1
1: proc 0 (:init::1) creates proc 1 (step1)
1: proc 0 (:init::1) pml_testing/transition_testing.pml:16 (state 1) [(run step1())]
Starting step2 with pid 2
2: proc 0 (:init::1) creates proc 2 (step2)
2: proc 0 (:init::1) pml_testing/transition_testing.pml:17 (state 2) [(run step2())]
3: proc 1 (step1:1) pml_testing/transition_testing.pml:6 (state 1) [globA = 1]
4: proc 2 (step2:1) pml_testing/transition_testing.pml:11 (state 1) [globB = 2]
4: proc 2 (step2:1) terminates
4: proc 1 (step1:1) terminates
4: proc 0 (:init::1) terminates
3 processes created
订单是proc 0 - > 0 - > 0 - > 0 - > 1 - > 2 - > 2 - > 1 - > 0
运行3:
0: proc - (:root:) creates proc 0 (:init:)
Starting step1 with pid 1
1: proc 0 (:init::1) creates proc 1 (step1)
1: proc 0 (:init::1) pml_testing/transition_testing.pml:16 (state 1) [(run step1())]
Starting step2 with pid 2
2: proc 0 (:init::1) creates proc 2 (step2)
2: proc 0 (:init::1) pml_testing/transition_testing.pml:17 (state 2) [(run step2())]
3: proc 2 (step2:1) pml_testing/transition_testing.pml:11 (state 1) [globB = 2]
3: proc 2 (step2:1) terminates
4: proc 1 (step1:1) pml_testing/transition_testing.pml:6 (state 1) [globA = 1]
4: proc 1 (step1:1) terminates
4: proc 0 (:init::1) terminates
3 processes created
订单是proc 0 - > 0 - > 0 - > 0 - > 2 - > 2 - > 1 - > 1 - > 0
我试图简单地得到它的输出:proc 0 - > 0 - > 0 - > 0 - > 1 - > 1 - > 2 - > 2 - > 0
我意识到proc 0不会终止,直到1和2终止,但为什么2和1的终止是非确定性地交错?当init函数是原子函数时,为什么SPIN在执行proc 1和proc 2之间随机选择,因此应该按顺序执行?为什么我可以在proc 1(在运行3中)之前启动和终止proc 2,但不是相反?
注意:我还使用dstep
而不是atomic
对此进行了测试,结果相同。
答案 0 :(得分:1)
首先,让我尝试为您的每个问题提供简短答案:
1。我意识到proc 0无法终止,直到1和2终止,但为什么2和1的终止是非确定性地交错?
流程始终以确定性方式终止:2
在1
之前终止,1
在0
和0
之前终止总是最后一个。但是,进程终止没有什么特别之处:它只是进程占用的最终转换。因此,在具有(立即)可执行指令的多个进程的任何时间点都可以进行进程交错。
2. 当init函数是原子函数时,为什么SPIN会在执行proc 1和proc 2之间随机选择,因此应该按顺序执行?
虽然init
确实原子地执行了他的两个指令,但要记住的重要事实是step1
和step2
是独立的进程并且在{{1}之后执行退出其原子块:init
不是函数调用,它只是生成环境中的进程,绝对不能保证这样的进程立即执行。根据衍生进程是否具有任何可执行指令,当前正在执行的进程是否处于原子序列以及非确定性调度程序进程选择的结果,这可能会发生也可能不会发生。 / p>
3。为什么我可以在proc 1(在运行3中)之前启动和终止proc 2,但不是相反?
在 Promela 中,进程只能按其创建的相反顺序死亡,如docs中所示:
run
因此,When a process terminates, it can only die and make its _pid number
available for the creation of another process, if and when it has the
highest _pid number in the system. This means that processes can only
die in the reverse order of their creation (in stack order).
可以在2
之前终止,因为它具有更高的1
值,而_pid
必须等待1
才能终止它
4. :SPIN如何决定原子流程中流程执行的顺序?
如果您的系统中有多个原子进程,则不存在此类问题。即使将整个进程封装在 atomic 关键字中,终止步骤仍然 原子块。调度程序永远不会中断执行原子序列的进程,除非进程在不可执行的指令前面阻塞。在这种情况下,原子性丢失,并且可以安排任何其他进程执行,如docs中所述:
如果原子序列中的任何语句阻塞,原子性丢失, 然后允许其他进程开始执行语句。 当被阻止的语句再次变为可执行时,执行 原子序列可以随时恢复,但不一定 立即。在进程可以恢复原子执行之前 在序列的其余部分,该过程必须首先与所有人竞争 系统中的其他活动过程重新获得控制权,即它 必须首先安排执行。
在您的问题中,您声明您的目标是获取以下执行流程:
2
在您的代码示例中,此执行流程是禁止,因为它使进程proc 0 -> 0 -> 0 -> 0 ->1 -> 1 -> 2 -> 2 -> 0
在进程1
之前终止,并且不允许这样做规则(见第三个问题的答案)。
注意:我也使用dstep而不是原子测试了这个,我得到了相同的结果。
原子块中的任何语句都无法阻止,因此在代码中使用2
或d_step
之间绝对没有区别。不过,我邀请您阅读this answer,了解原子和 d_step 之间的相似点和不同点。
示例执行流程
其次,让我根据执行流程的分析尝试更深答案级别。
在您的代码示例中,有三个流程。
atomic
(始终)要创建的第一个进程(如果可用),因此它是 (始终)已分配init
等于_pid
并已安排在第一位。由于0
进程的整个主体都包含在原子块中,因此该进程执行init
和run step1();
而不与其他进程交错。流程run step2()
被指定为step1
等于_pid
,因为它是要创建的第二个流程,而流程1
被指定为step2
等于{{} 1}},因为它是要创建的第三个进程。
在您的示例中,在_pid
进程到达原子的末尾之前,无法安排进程2
和step1
执行代码示例与step2
代码的结尾一致。
当init
到达其正文的末尾时,该进程(init
等于init
)不会死亡,因为在环境中至少有一个进程_pid
1}}值大于他自己的值,即0
和_pid
。虽然step1
已被屏蔽,但step2
和init
都可以执行,因此非确定性调度程序可以任意选择step1
或{ {1}}执行。
如果先安排step2
,那么它会执行唯一的指令step1
而不与step2
交错。请注意,由于原子块中只有一条指令,并且此指令本身是原子的,因此原子块是多余的(同样适用于{{1} })。同样,由于step1
的{{1}}等于globA = 1;
,并且仍然存在一个值step2
更高的进程,因此进程step2
不会死亡。此时,可以安排执行的唯一进程是step1
,它也可以终止,因为没有具有更高_pid
值的进程。这允许1
终止,这反过来也允许_pid
死亡。此执行流程对应于运行2 。
如果先安排step1
,那么此过程将值step2
分配给_pid
并到达其正文的末尾,即外< / strong> atomic 块,有两种可能的执行流程:
案例A)调度程序非确定性地选择step1
再次执行,init
终止;现在,调度程序唯一可用的选项是使step2
执行自己的指令,使其终止,然后允许2
终止。此执行流程对应于运行1 。
案例B)调度程序非确定性地选择要执行globB
,step2
将step2
分配给step1
,但无法终止因为init
还活着;唯一可以调度的进程是step1
,因此后者在被调度程序选中后终止,允许step1
和1
以级联方式终止。此执行流程对应于运行3 。
线性执行:
实现线性执行的最简单,最明显的方法是在模型中只包含一个进程。了解为什么会这样,这是微不足道的。所以你的例子将成为:
globA
不再需要在此代码中使用原子块,因为只有一个进程。当然,你可能不赞成这样一个简单的解决方案,所以让我们看看另一个基于全局变量的解决方案:
step2
与您的代码示例不同,由于同步变量step2
,step1
执行init
之前永远无法执行int globA;
int globB;
inline step1()
{
globA = 1;
}
inline step2()
{
globB = 2;
}
init
{
step1();
step2();
}
。但是,与您的代码示例类似,进程int globA;
int globB;
bool terminated;
proctype step1()
{
globA = 1;
terminated = true;
}
proctype step2()
{
globB = 2;
terminated = true;
}
init
{
run step1();
terminated -> terminated = false;
run step2();
terminated -> terminated = false;
}
和globB = 2
的实际终止受交错影响。 ie 如果globA = 1
立即终止,那么只有在terminated
完全释放其拥有的资源后才会创建step1
,然后step2
被分配{ {1}}等于step1
;否则,step2
被指定为step1
等于step2
。
我能想到的最佳解决方案基于消息传递的概念。基本上,我们的想法是只允许在任何给定时间点安排当前持有令牌的流程,并以所需的调度顺序传递此类令牌 :
_pid
请注意,此解决方案强制只有一种可能的计划。这可以通过运行交互式模拟来验证:
1
如您所见,用户永远不会有机会做出任何选择,因为只有一个可能的执行流。这显然是因为 A)我没有在step2
之前和_pid
B之后之前放置任何指令所需的调度顺序重合以及创建流程的顺序。