FSP语言学习(九):安全性和活泼性属性

目录

1. 引言

2. Safety

2.1 Safety Properties

2.2 Safety Property for Mutual Exclusion

3. 单线桥问题

3.1 Single-Lane Bridge Model

4. Liveness

4.1 Progress Properties

4.2 Progress Analysis

4.3 Action Priority

5. Liveness of the Single-Lane Bridge

5.1 Revised Single-Lane Bridge Model

5.2 修订后的单线桥实施方案

6. Readers–Writers Problem

6.1 Safety Property

6.2 Progress Property


1. 引言

属性是一个程序的属性,对于该程序的每一个可能的执行都是真的。对并发程序感兴趣的属性分为两类:安全和有效性。安全性属性断言,在执行过程中不会发生任何坏事。活性属性断言,最终会有好事发生。另一种说法是:

安全性是指程序不会达到坏的状态,而活性是指程序最终达到一个好的状态。

在顺序程序中,最重要的安全属性是最终状态是正确的。我们已经看到,对于并发程序,重要的安全属性是相互排斥和没有死锁。在上一篇博文中,我们确定死锁通常是一种坏的状态,在这种状态下不能执行进一步的行动。之前,我们还学到,允许一个以上的进程访问一个共享变量会导致干扰,从而导致不正确或不好的状态。

对于一个顺序程序来说,最重要的活泼性属性是它最终会终止。然而,在并发编程中,我们经常关注的是不会终止的系统。在本篇中,我们主要处理与资源访问有关的活性问题:进程对共享资源的请求最终是否被批准?我们将看到,有效性属性受到调度策略的影响,调度策略决定在一组符合条件的行动中选择哪些行动来执行。

这一篇解释了我们如何分析并发系统的有限状态模型,以解决安全和活泼性问题。用汽车通过单车道桥的例子来集中讨论。然后我们分析了一些read/write锁的实现。read/write锁允许许多进程同时访问一个共享资源,进行读取访问,但要求写入访问是相互排斥的。它们出现在许多并发程序中。

2. Safety

在上一篇博文中,我们看到LTSA分析工具在对应于FSP模型的标记过渡系统上使用广度优先搜索来执行死锁分析。作为搜索目标的 "坏 "状态是那些没有输出转换的状态,也就是死锁状态。除了死锁状态之外,搜索还在寻找ERROR状态。在LTS中,这些状态有一个唯一的标识(-1),以示区别。到目前为止,我们使用了由本地进程ERROR明确表示的ERROR状态。下图中给出的例子是一个执行器,在它对第一个命令做出反应之前,不得接收第二个命令。

由于没有控制系统来确保执行器不会收到多个未被确认的命令,由LTSA进行的安全分析产生了以下的traces:

Trace to property violation in ACTUATOR:
     command
     command

我们指定了被视为错误的情况,而不是直接表达我们希望保留的所需安全属性。在致动器的例子中,安全属性是一个命令动作必须总是由一个响应动作跟随,而没有一个干预命令。在复杂的系统中,通常通过直接说明所需的东西而不是说明不需要的东西来指定安全属性会更好。通过这种方式,我们可以把注意力集中在系统的理想行为上,而不是试图列举所有可能的不理想行为。

2.1 Safety Properties

在FSP中,安全属性是由属性进程指定的。从语法上讲,这些进程只是以关键字property为前缀的FSP进程。它们与目标系统组成,以确保指定的属性在该系统中成立。下图中的例子指定了进入房间前敲门是有礼貌的这一属性。

上图的LTS图显示,在翻译一个属性流程时,编译器会自动生成到ERROR状态的转换。很容易看出,在敲击动作之前的进入动作会导致向ERROR状态的转换。此外,敲击两次是对POLITE属性的违反。应该注意的是,属性进程的每个状态中,其字母表中的所有动作(enter,knock)都是合格的选择。那些不属于安全属性所允许的行为是向ERROR状态的过渡。所有的属性过程都是如此。

检查上图中ACTUATOR的正确操作的属性是简单的。

property SAFE_ACTUATOR
     =(command->respond->SAFE_ACTUATOR).

属性进程可以与一个系统组成而不影响该系统的正确行为。换句话说,将一个属性进程与一组进程组成并不影响它们的正常运行。然而,如果可能发生违反安全属性的行为,那么就会产生向ERROR状态的转换。为了保持安全属性的这种透明度,属性进程必须是确定性的。也就是说,它们必须不包含非确定性的选择。经验表明,这在实践中很少是一种限制。

一个安全属性P定义了一个确定的进程,它断言任何包括P的字母表中的行动的跟踪,都被P接受。

因此,如果P与S组成,那么在S的字母表和P的字母表中的动作traces,也必须是P的活性traces,否则ERROR是可以达到的。我们可以通过使用字母表扩展来指定一个动作或一组动作不应该发生。下图的例子断言,灾难永远不应该发生。

2.2 Safety Property for Mutual Exclusion

下面列出的是SEMADEMO模型的一个稍加修改的版本,以显示信号如何被用来确保相互排斥。这个修改是用enter和exit的动作来代替单一的critical动作,这些动作模拟了进入和离开critical部分的情况,其中需要对共享资源进行互斥访问。

LOOP =
    (mutex.down->enter->exit->mutex.up->LOOP).

||SEMADEMO = (p[1..3]:LOOP
              ||{p[1..3]}::mutex:SEMAPHORE(1)).

为了验证这个系统确实能确保相互排斥,我们可以指定一个相互排斥的属性,并将其与系统组成如下:

property MUTEX =
      (p[i:1..3].enter->p[i].exit->MUTEX).

||CHECK = (SEMADEMO || MUTEX).

安全属性MUTEX规定,当一个进程进入临界区(p[i].enter)时,同一进程必须在另一个进程进入之前退出临界区(p[i].exit)。在目前的系统中,这个属性没有被违反;但是,如果我们把初始化信号的值从1改为2(即SEMAPHORE(2)),那么使用LTSA的安全分析就会产生以下的跟踪。

Trace to property violation in MUTEX:
     p.1.mutex.down
     p.1.enter
     p.2.mutex.down
     p.2.enter

由于有两个进程进入了临界区,该traces显然是违反了互斥原则。

3. 单线桥问题

这个问题在下图中得到了描述。一座横跨河流的桥梁,其宽度只够允许一条车道通行。因此,汽车只有在同一方向行驶时才能同时行驶。如果两辆不同方向行驶的汽车同时进入桥上,就会发生安全违规。

为了澄清讨论,我们把从左到右移动的汽车称为红车,从右到左移动的汽车称为蓝车。在我们的并发编程模型中,每辆汽车都是一个进程,问题是要确保在不同方向行驶的汽车不能并发地访问共享资源,即桥梁。为了使模拟更加真实,我们还必须确保在同一方向上行驶的汽车不能互相通过。

3.1 Single-Lane Bridge Model

在对单车道桥进行建模时,我们使用以下常数和范围的定义:

const N = 3    // number of each type of car
range T = 0..N // type of car count
range ID= 1..N // car identities

问题的实质是进入桥梁,所以汽车参与的唯一感兴趣的事件是进入桥梁和离开桥梁。因此,汽车是由一个反复进入和离开大桥的过程来模拟的。

CAR = (enter->exit->CAR).

为了模拟汽车在桥上不能相互通行的事实,我们需要以下进程,这些进程分别制约着进入和离开行动的顺序:

NOPASS1  = C[1], //preserves entry order
C[i:ID]  = ([i].enter->C[i%N+1]).

NOPASS2  = C[1], //preserves exit order
C[i:ID]  = ([i].exit->C[i%N+1]).

||CONVOY = ([ID]:CAR||NOPASS1||NOPASS2).

CONVOY进程模拟了一组在同一方向行驶的汽车,它们一个接一个地进入大桥,一个接一个地离开大桥。然而,在下一辆车进入之前,它不会阻止一辆车离开桥梁。所有汽车的行为都由以下构成来体现:

||CARS = (red:CONVOY || blue:CONVOY).

剩下的必须被建模的实体是桥本身。它必须约束CARS,使一个或多个在同一方向行驶的汽车可以同时在桥上行驶,而在不同方向行驶的汽车则不能。为了执行这一点,桥上有一个蓝色汽车和红色汽车的计数。只有当蓝车计数为零时,红车才被允许进入,反之亦然。BRIDGE的流程如下。

BRIDGE = BRIDGE[0][0], // initially empty
BRIDGE[nr:T][nb:T] =   //nr is the red count, nb the blue
      (when (nb==0)
         red[ID].enter -> BRIDGE[nr+1][nb]
      |red[ID].exit    -> BRIDGE[nr-1][nb]
      |when (nr==0)
         blue[ID].enter-> BRIDGE[nr][nb+1]
      |blue[ID].exit   -> BRIDGE[nr][nb-1]
      ).

请注意,桥的退出动作允许车数nr和nb被递减,尽管它们的值是0。 LTSA工具中的FSP编译器将自动把这些未定义的状态映射到ERROR状态,通过发出警告来表示。

Warning - BRIDGE.-1.0 defined to be ERROR
Warning - BRIDGE.0.-1 defined to be ERROR
...

事实上,当BRIDGE与CARS组成时,它们的行为会阻止未进入的汽车退出,并且ERROR状态是不可达的。

在描述整个系统组成之前,我们需要指定一个安全属性与系统组成,以验证汽车在桥上不发生碰撞。下面列出了所需的属性。它规定,当红色汽车在桥上时,只有红色汽车可以进入,当蓝色汽车在桥上时,只有蓝色汽车可以进入。当桥是空的时候,红车或蓝车都可以进入。索引i用来计算目前在桥上的红(或蓝)车。

property ONEWAY =(red[ID].enter   -> RED[1]
                 |blue[ID].enter -> BLUE[1]
                 ),
RED[i:ID] = (red[ID].enter -> RED[i+1]
            |when(i==1)red[ID].exit -> ONEWAY
            |when(i>1) red[ID].exit -> RED[i-1]
            ),
BLUE[i:ID]= (blue[ID].enter -> BLUE[i+1]
            |when(i==1)blue[ID].exit -> ONEWAY
            |when(i>1) blue[ID].exit -> BLUE[i-1]
            ).

现在,整个系统可以通过下图中规定的复合过程进行建模。

使用LTSA的安全分析验证了ONEWAY的安全属性没有被违反。

然而,如果没有BRIDGE提供的约束,组合(CARS||ONEWAY)会产生以下的安全违反。

Trace to property violation in ONEWAY:
     red.1.enter
     blue.1.enter

4. Liveness

活性属性断言,好的事情最终会发生。对于单车道桥来说,一个合理的有效性属性是所有红色和蓝色的汽车最终都能过桥。正如我们所看到的,上一节开发的程序在每种类型的汽车都有三辆的情况下并不满足这个属性。在这一节中,我们将看到如何对模型进行活性分析。就像死锁和其他安全属性一样,我们的目标是在建模阶段解决活性问题,这样它们就不会在实现的程序中出现。

一个完全通用的对活性的处理是相当复杂的,需要使用一个时间逻辑来指定所需的活性属性。为了不带来一种形式主义的负担,我们处理了一类有限的活属性,我们称之为进度。进度属性断言,无论系统处于什么状态,最终都会执行一个指定的动作。进度与饥饿相反,饥饿是一种并发编程情况的名称,在这种情况下一个动作永远不会被执行。进度属性很容易指定,而且足够强大,可以捕捉到并发程序中广泛的有效性问题。

4.1 Progress Properties

为了说明进度的概念,我们使用一个简单的例子,即抛掷硬币的例子:

如果硬币被抛出无限次,我们就会想到,正面会被无限次选择,而反面会被无限次选择。事实上,这取决于用于决定从符合条件的过渡集合中执行哪个过渡的调度政策。如果政策不公平,那么我们总是可以选择导致头的抛物过渡。我们假设选择的调度政策是公平的,定义如下。

公平选择:如果对一组过渡的选择被无限地执行,那么这组过渡中的每个过渡都将被无限地执行。

如果一个动作的转换(或过渡)在一个系统中无限频繁地发生,我们可以说,在执行的任何阶段,总是该动作最终会发生。在公平选择的假设下,掷硬币的系统最终应该选择正面,最终选择反面。我们可以用FSP中规定的进度属性来断言这一点。一个进度属性的定义是:

progress P = {a1,a2...an}定义了一个进度属性P,它断言在目标系统的无限执行中,至少有一个动作a1,a2...an将被无限地执行。

换句话说,一个进度属性断言,在执行的任何阶段,进度集中的一个动作最终会发生。抛硬币的有活性要求现在可以表示为:

progress HEADS = {heads}
progress TAILS = {tails}

到目前为止,我们所定义的COIN系统满足了这些特性。我们现在研究一个不满足的系统。假设首先抛出硬币的代理人从两枚硬币中挑选了一枚:下图中定义的有头有尾的正常硬币和两面都有头的诡计硬币。抛出诡计硬币的结果必须始终是正面。这个系统的模型见图。

根据进度属性HEADS和TAILS对TWOCOIN系统进行进度分析,产生以下输出。

Progress violation: TAILS
Path to terminal set of states:
     pick
Actions in terminal set:
{toss, heads}

这证实了预期的结果:如果代理人选择了诡计的硬币,那么行动的尾巴就不会出现。这当然违反了TAILS的进度属性,该属性断言在一个无限的执行中,尾巴必须无限地出现。读者应该注意,上图的系统并不违反进度属性。

progress HEADSorTAILS = {heads,tails}

属性HEADS或TAILS没有被违反,因为在进度集中只有一个动作需要被无限次地执行以满足该属性。

4.2 Progress Analysis

进度分析包括首先进行终端状态集的搜索。

终端状态集是指每个状态都可以通过一个或多个转换从该集的其他状态中到达,并且没有从该集内到该集外任何状态的转换。

在图论中,这被称为强连接组件,它没有通往该组件中的节点集以外的任何节点的路径。例如,上一个模型图的标记过渡系统有两个终端状态集,{1,2},是与诡计硬币有关的状态,{3,4,5},是与正常硬币有关的状态。

一个由有限状态集代表的系统的执行,只有在某些状态被无限次访问的情况下才是无限的。在一个执行中被无限次访问的状态必须形成一个终端集。考虑到公平的选择,每个终端状态集代表一个执行,在这个执行中每个过渡都被无限地执行。由于没有走出终端集的过渡,任何没有在所有终端集中使用的动作都不可能在系统的所有执行中无限地出现。现在,检查一个进度属性是否成立仅仅是检查在每个终端集中,至少有一个进度集中的动作作为一个过渡出现。反过来说,如果分析发现一个终端状态集,其中没有一个进度集的动作出现,那么就违反了进度属性。对于TAILS属性,这个终端集是动作尾巴不出现的状态{1,2}的集合。输出给出了到终端集根部的最短执行路径,并列出了确实出现在该集中的动作。

如果没有指定进度属性,LTSA使用一个默认属性来执行进度分析。这个属性断言,对于目标系统的字母表中的每一个动作,鉴于公平的选择,该动作将被无限地执行。这相当于为每个动作指定了一个单独的进度属性。对于TWOCOIN系统,这个默认分析产生了以下输出。

Progress violation for actions:
{pick}
Path to terminal set of states:
     pick
Actions in terminal set:
{toss, heads, tails}

Progress violation for actions:
{pick, tails}
Path to terminal set of states:
     pick
Actions in terminal set:
{toss, heads}

分析产生了两个进度违反,因为在任何一个终端集中,动作pick都不是无限次执行的。这个默认属性的价值在于,如果它没有被违反,那么任何指定的进度属性都不能被违反。换句话说,如果默认属性成立,那么以目标系统的动作字母表的子集为单位指定的其他每一个进度属性也必须成立。这是真的,因为默认属性断言每个动作都会被无限次地执行。所有状态发生在单一终端集内的系统都满足默认进度属性。

4.3 Action Priority

如果默认的进度分析被应用于单车道桥梁模型,那么就不会检测到违规行为。然而,我们从实施中得知,有可能发生违反进度的情况。无论是蓝车还是红车都可能永远等待过桥。为什么我们没有发现这些进度问题?

答案就在于作为进度测试基础的公平选择假设。这意味着系统的每一个可能的执行最终都会发生,包括那些汽车不会饿死的情况。为了检测进度问题,我们必须为行动叠加一些调度策略,模拟桥梁被大量使用的情况,也就是说,我们需要施加不利的条件,对系统进行 "压力测试"。我们使用行动优先级表达式来描述这些调度策略。在FSP中,行动的优先级是相对于进程的组成而指定的。

高优先级操作符("<<")
||C = (P||Q) <<{a1,...,an}指定一个组合,其中行动a1,...,an比P||Q的字母表中的任何其他行动(包括沉默的行动tau)具有更高的优先权。在这个系统的任何选择中,如果有一个或多个动作a1,...,an标记了一个过渡,那么标记了低优先级动作的过渡将被丢弃。

低优先级操作符(">>")。
||C = (P||Q)>>{a1,...,an }指定一个组合,其中行动a1,...,an的优先级低于P||Q字母表中的任何其他行动,包括沉默行动tau。在这个系统的任何选择中,如果有一个或多个过渡没有被a1,...,an标示,那么被a1,...,an标示的过渡将被丢弃。

动作优先级运算符通过舍弃特定的过渡来简化复合过程。下图说明了一个简单例子的效果。当工作在组合HIGH中被指定为高优先级的动作时,睡眠过渡消失了,因为它的优先级较低,因此在睡眠和工作之间的选择中,工作总是被选择。当工作被指定为构成LOW中的低优先级动作时,工作转换就会消失,因为它的优先级较低,因此在睡眠和工作之间的选择中,总是会选择睡眠。

5. Liveness of the Single-Lane Bridge

利用进度属性和行动优先级,我们现在可以研究单车道桥的失效问题。特别是,我们对以下两个进度属性感兴趣,当桥梁处于重度负载或拥堵状态时。

progress BLUECROSS = {blue[ID].enter}
progress REDCROSS = {red[ID].enter}

BLUECROSS断言,总是有一辆蓝色汽车能够进入桥上;REDCROSS对红色汽车也是如此断言。这就留下了如何使用行动优先权来模拟拥堵的问题。如果我们给所有与红色汽车有关的行动以高于蓝色汽车的优先权,我们会得到BLUECROSS被违反的情况,同样,如果我们给蓝色汽车以优先权,REDCROSS被违反。这两种调度策略都不是程序的良好模型。在执行中,红色和蓝色汽车都没有优先权。相反,我们给汽车从桥上出来的优先权很低。这模拟了桥梁拥堵的情况,因为在另一辆车进入桥梁和一辆车离开桥梁的任何选择中,我们选择让一辆车进入。拥挤的桥梁的模型是:

||CongestedBridge = (SingleLaneBridge)
                    >>{red[ID].exit,blue[ID].exit}.

根据属性BLUECROSS和REDCROSS对这个系统进行进度分析,产生了以下输出。

Progress violation: BLUECROSS
Path to terminal set of states:
     red.1.enter
     red.2.enter
Actions in terminal set:
{red.1.enter, red1.exit, red.2.enter,
red.2.exit, red.3.enter, red.3.exit}
Progress violation: REDCROSS
Path to terminal set of states:
     blue.1.enter
     blue.2.enter
Actions in terminal set:
{blue.1.enter, blue.1.exit, blue.2.enter,
blue.2.exit, blue.3.enter, blue.3.exit}

输出与程序的观察结果相一致。当有三辆车,并且一辆红色的车先进入时,桥就会被红色的车连续占据,而蓝色的车永远不会通过。同样地,如果一辆蓝车先进入,红车也不会通过。然而,该模型对一些程序细节进行了抽象,如桥的长度,以及因此而需要连续占用的汽车数量。因此,当每个方向只有两辆汽车在移动时,该模型会检测到缺乏进展。在下图所描述的过渡系统中,可以清楚地看到这种情况的终端状态集。

当每个方向只有一辆车行驶时,桥梁不会变得拥挤,红车和蓝车都会取得进展。下图描述了单车情况下的过渡系统。

如果我们转而通过给予汽车进入桥梁的高度优先权来模拟拥堵,我们会得到同样的进展结果吗?有兴趣的应该检查一下情况是否确实如此。

我们现在必须做的是设计一个模型,当每个方向上有超过一辆车行驶时,不会出现进度问题。

5.1 Revised Single-Lane Bridge Model

一座在任何时候都能动态地决定是接纳蓝色汽车还是红色汽车的桥梁,需要有比目前模型中更多的关于汽车状态的信息。特别是,大桥需要知道汽车是否在等待通过。为此,对汽车的模型进行了修改,使其在试图进入大桥之前请求进入大桥。修改后的汽车模型为:

CAR = (request->enter->exit->CAR).

桥梁模型现在可以计算在两端等待的汽车数量。当汽车请求进入时,这个计数被递增,当汽车进入桥时被递减。我们对修改后的BRIDGE过程的第一次尝试,使用了这种对等待车辆的计数,具体如下。只有当桥上没有蓝色汽车,也没有蓝色汽车在等待时,红色汽车才被允许进入桥上。只有当桥上没有红色汽车,也没有红色汽车在等待进入桥上时,才允许蓝色汽车进入桥上。修订后的BRIDGE流程如下:

/* nr - number of red cars on the bridge
   nb - number of blue cars on the bridge
   wr - number of red cars waiting to enter
   wb - number of blue cars waiting to enter
*/
BRIDGE = BRIDGE[0][0][0][0],
BRIDGE[nr:T][nb:T][wr:T][wb:T] =
  (red[ID].request  -> BRIDGE[nr][nb][wr+1][wb]
  |when (nb==0 && wb==0)
    red[ID].enter   -> BRIDGE[nr+1][nb][wr-1][wb]
  |red[ID].exit     -> BRIDGE[nr-1][nb][wr][wb]
  |blue[ID].request -> BRIDGE[nr][nb][wr][wb+1]
  |when (nr==0 && wr==0)
    blue[ID].enter  -> BRIDGE[nr][nb+1][wr][wb-1]
  |blue[ID].exit    -> BRIDGE[nr][nb-1][wr][wb]
  ).

这个模型的问题是,当我们检查新的SingleLaneBridge系统的安全属性时,会报告一个死锁。

Trace to DEADLOCK:
      red.1.request
      red.2.request
      red.3.request
      blue.1.request
      blue.2.request
      blue.3.request

traces是两端都有汽车在等待的情况,因此,桥不允许红色或蓝色汽车进入。为了解决这个问题,我们必须在问题中引入一些不对称性(就像对餐饮哲学家所做的那样)。这就是布尔变量(bt)的形式,它表明是轮到蓝色汽车还是红色汽车进入桥。最初,bt被设置为真,表示轮到蓝色的车。一旦有蓝车出桥,bt就被设置为假。当一辆红车离开时,bt又被设置为真。BRIDGE过程就变成了。

const True = 1
const False = 0
range B = False..True
/* nr - number of red cars on the bridge
   nb - number of blue cars on the bridge
   wr - number of red cars waiting to enter
   wb - number of blue cars waiting to enter
   bt - true indicates blue turn,
        false indicates red turn
*/
BRIDGE = BRIDGE[0][0][0][0][True],
BRIDGE[nr:T][nb:T][wr:T][wb:T][bt:B] =
  (red[ID].request ->BRIDGE[nr][nb][wr+1][wb][bt]
  |when (nb==0 && (wb==0||!bt))
    red[ID].enter  ->BRIDGE[nr+1][nb][wr-1][wb][bt]
  |red[ID].exit    ->BRIDGE[nr-1][nb][wr][wb][True]
  |blue[ID].request->BRIDGE[nr][nb][wr][wb+1][bt]
  |when (nr==0 && (wr==0||bt))
    blue[ID].enter ->BRIDGE[nr][nb+1][wr][wb-1][bt]
  |blue[ID].exit   ->BRIDGE[nr][nb-1][wr][wb][False]
).

桥上允许红车进入的条件是:桥上没有蓝车,并且没有蓝车在等待或者没有轮到蓝车:nb==0 &&(wb==0 || !bt)。蓝车进入的条件是,桥上没有红车,并且要么没有红车在等待,要么轮到蓝车:nr==0 &&(wr==0 || bt)。

这个修正后的模型不再出现死锁。此外,一个进度分析报告显示,BLUECROSS和REDCROSS属性没有被违反。

5.2 修订后的单线桥实施方案

对程序的修改涉及到一个新版本的桥梁监控器,它准确地实现了上一节中开发的模型中的BRIDGE过程。事实上,我们不需要引入一个新的监视器方法来实现汽车所做的请求动作。现有的enter方法可以被修改为在测试调用者是否可以访问桥梁之前递增一个等待计数。和以前一样,这些测试只是对模型BRIDGE过程中的守卫的否定。新的实现被列在下列程序中。

class FairBridge extends Bridge {
  private int nred = 0;  //count of red cars on the bridge
  private int nblue = 0;  //count of blue cars on the bridge
  private int waitblue = 0;    //count of waiting blue cars
  private int waitred = 0;     //count of waiting red cars
  private boolean blueturn = true;

  synchronized void redEnter()
      throws InterruptedException {
    ++waitred;
    while (nblue>0||(waitblue>0 && blueturn)) wait();
    --waitred;
    ++nred;
  }

  synchronized void redExit(){
    --nred;
    blueturn = true;
    if (nred==0)notifyAll();
  }

  synchronized void blueEnter(){
      throws InterruptedException {
    ++waitblue;
    while (nred>0||(waitred>0 && !blueturn)) wait();
    --waitblue;
    ++nblue;
  }

  synchronized void blueExit(){
    --nblue;
    blueturn = false;
    if (nblue==0) notifyAll();
  }
}

在演示的小程序中,当Fair check box被点击时,就会使用这个监视器的实现。

6. Readers–Writers Problem

读取者——写入者问题涉及到两种进程对共享数据库的访问。读取者执行检查数据库的事务,而写入者同时检查和更新数据库。为了使数据库得到正确的更新,Writer在更新数据库时必须有对数据库的独占访问权。如果没有Writer在访问数据库,那么任何数量的Readers都可以同时访问它。在这一节中,我们开发了一个问题的解决方案。像往常一样,在进行实现之前,我们构建了一个问题的模型,以检查其安全性和活性属性。

在对问题进行建模时,第一步是决定感兴趣的行动。这些行动是获取和释放对共享数据库的读取访问以及获取和释放写入访问。这些行动在下面被声明为 "行动 "集合。

set Actions = {acquireRead,releaseRead,
               acquireWrite,releaseWrite}

观赏园模型,我们使用一个集合常数,只是作为模型描述的一种缩写方式。为读者和写作者建模的过程是:

READER =
  (acquireRead->examine->releaseRead->READER)
  +Actions
  \ {examine}.

WRITER =
  (acquireWrite->modify->releaseWrite->WRITER)
  +Actions
  \ {modify}.

一个READER进程在检查数据库之前必须获得读取权限,一个WRITER在修改数据库之前必须获得写入权限。这两个进程的字母表已经被定义为字母表扩展+Actions的全部访问动作。这确保了当一个READER只进行acquisitionRead和releaseRead动作时,acquisitionWrite和releaseWrite动作对于进程的任何前缀实例都不能自由发生。同样地,对于WRITER进程来说,acquisitionRead和releaseRead动作也不能自由发生。检查和修改动作是隐藏的,因为它们与同步访问共享数据库的问题无关。

对共享数据库的访问是由一个读/写锁控制的。当它还没有被acquisitionWrite获取写入权限时,该锁接受acquisitionRead动作。当它没有被获取用于读取访问时,它只允许单一的写入访问。该锁是由RW_LOCK进程建模的。

const False = 0   const True = 1
range Bool  = False..True
const Nread = 2           // Maximum readers
const Nwrite= 2           // Maximum writers

RW_LOCK = RW[0][False],
RW[readers:0..Nread][writing:Bool] =
      (when (!writing)
         acquireRead ->RW[readers+1][writing]
      |releaseRead   ->RW[readers-1][writing]
      |when (readers==0 && !writing)
         acquireWrite->RW[readers][True]
      |releaseWrite  ->RW[readers][False]
      ).

RW_LOCK进程维护一个并发的读访问次数(读者)和一个布尔值(写),当获得写访问的锁时,布尔值被设置为真。获取读访问的动作只有在写为假时才被接受,获取写访问的动作只有在读者==0且写为假时才被接受。

6.1 Safety Property

为了检查锁的行为是否符合要求,我们定义了一个安全属性,RW_SAFE,如下所示。

property SAFE_RW
  = (acquireRead->READING[1]
    |acquireWrite->WRITING
    ),
READING[i:1..Nread]
  = (acquireRead->READING[i+1]
    |when(i>1) releaseRead ->READING[i-1]
    |when(i==1)releaseRead ->SAFE_RW
    ),
WRITING = (releaseWrite->SAFE_RW).

该属性断言,最初可以接受获取读取动作或获取写入动作。换句话说,当锁是自由的,它可以被获取为读或写访问。当被获取为读访问(READING)时,允许进一步的acquisitionRead动作,但没有acquisitionWrite动作。直到所有与acquisitionRead动作相对应的releaseRead动作发生后,锁才变得自由。当锁被获取为写访问(WRITING)时,只应发生释放写动作。为了检查锁的实现RW_LOCK是否满足该属性,该锁与该属性组成如下。

||READWRITELOCK = (RW_LOCK || SAFE_RW).

上图的ERROR状态的转换是在一个读写器行为不良的情况下发生的。例如,如果一个读卡器在没有执行过acquisitionRead的情况下执行了releaseRead,那么就会发生安全违规。如果发出两个以上的acquisitionRead请求,也会发生违规。

下图描述了带有读/写锁的READER和WRITER进程的组成。对这个系统的分析显示,没有死锁或安全违规。行为良好的READER和WRITER进程的加入确保了下图的错误转换不会发生。

6.2 Progress Property

在读者-写作者系统中,重要的进度属性是,读者和写作者都应该最终获得对共享数据库的访问。我们可以将所需的进度属性表达如下。

progress WRITE = {writer[1..Nwrite].acquireWrite}
progress READ  = {reader[1..Nread].acquireRead}

WRITE属性断言,至少有一个WRITER进程可以执行acquisitionWrite动作,这应该是始终存在的情况。由于WRITER进程是完全对称的,我们可以合理地预期,如果一个进程可以获取Write,那么其他进程也可以。READ为READER进程和acquisitionRead规定了同样的属性。在READERS_WRITERS指定的系统中,进度检查报告没有违反这些属性。由于公平选择的假设,进度问题只发生在有错误的完整系统模型中。为了找到系统在加载或 "受压 "时的表现,我们必须使用行动优先权指定不利的调度条件。这正是我们在单线桥模型中寻找进度问题所采用的程序。事实上,不利条件与桥梁问题中使用的条件相似。为了建立一个重载的系统模型,我们给释放行动以较低的优先级,就像我们在桥梁问题中给出口行动以较低的优先级一样。(或者,我们可以给获取行动以更高的优先权。)用于进展分析的系统模型是这样描述的。

||RW_PROGRESS = READERS_WRITERS
                >>{reader[1..Nread].releaseRead,
                   writer[1..Nread].releaseWrite}.

对这个系统的分析导致了违规:

Progress violation: WRITE
Path to terminal set of states:
     reader.1.acquireRead
Actions in terminal set:
{reader.1.acquireRead, reader.1.releaseRead,
 reader.2.acquireRead, reader.2.releaseRead}

该违规行为描述了这样一种情况,即写作者不能访问共享数据库,因为一个读者总是有读的权限。换句话说,读者的数量永远不会下降到零,因此,读/写锁拒绝了对写者的访问。描述这种行为的终端状态集可以在下图中清楚地看到。它包含编号为3、4和5的状态。在探索这个进度问题的解决方案之前,我们在下一节将现有的模型转化为实现。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值