1 并发(concurrency)与死锁

1.1 例子

如课本P8的复杂贩卖机进程:
VMC=(in2p→(large→VMC∣small→out1p→VMC)∣in1p→(small→VMC∣in1p→(large→VMC∣in1p→STOP))) VMC = (in2p \to (large \to VMC | small \to out1p \to VMC) \\ | in1p \to (small \small \to VMC | in1p \to (large \to VMC | in1p \to STOP))) VMC=(in2p(largeVMCsmallout1pVMC)in1p(smallVMCin1p(largeVMCin1pSTOP)))
与下面这个愚蠢顾客的进程(在CSP中进程的环境可被描述成进程的形式):
FOOLCUST=(in2p→large→FOOLCUST ∣ in1p→large→FOOLCUST)FOOLCUST=(in2p \to large \to FOOLCUST \ | \ in1p \to large \to FOOLCUST)FOOLCUST=(in2plargeFOOLCUST  in1plargeFOOLCUST)
两者相并发,也就是具有该行为的顾客在使用该贩卖机的进程:
(VMC ∣∣ FOOLCUST)=μX⋅(in2p→large→X ∣ in1p→STOP)(VMC \ || \ FOOLCUST)=\mu X \cdot (in2p \to large \to X \ | \ in1p \to STOP)(VMC  FOOLCUST)=μX(in2plargeX  in1pSTOP)
这是因为,当FOOLCUSTFOOLCUSTFOOLCUST第一步选择in2pin2pin2p时,第一个VMCVMCVMC也要做此动作,两者同步执行,接下来VMCVMCVMC面临选择largelargelargesmallsmallsmall,而FOOLCUSTFOOLCUSTFOOLCUST接下来要做largelargelarge,那么两者继续向前同步执行,然后各自回到开头。

FOOLCUSTFOOLCUSTFOOLCUST第一步选择in1pin1pin1p时,第一个VMCVMCVMC也要做此动作,两者同步执行,接下来VMCVMCVMC面临选择smallsmallsmallin1pin1pin1p,而FOOLCUSTFOOLCUSTFOOLCUST接下来要做largelargelarge,那么两者在公共动作上存在分歧,无法继续执行,故进程进入STOPSTOPSTOP状态,此即发生了死锁(deadlock)

1.2 性质

显然,并发程序的tracestracestraces是其内所有并发程序的tracestracestraces的交集。关于并发的更多性质在课本P50~51。仅分析其中几个最关键的性质。

假定有进程PPPQQQ,其中aaaPPP的私有动作,bbbQQQ的私有动作,c,dc,dc,dPPPQQQ的公共动作,那么:

  • L4A:(c→P) ∣∣ (c→Q)=c→(P ∣∣ Q)(c \to P) \ || \ (c \to Q) = c \to (P \ || \ Q)(cP)  (cQ)=c(P  Q)
    这表示并发程序同步做同一动作ccc,接下来继续并发执行。

  • L4B:(c→P) ∣∣ (d→Q)=STOP     if c≠d(c \to P) \ || \ (d \to Q) = STOP \ \ \ \ \ if \ c \neq d(cP)  (dQ)=STOP     if c=d
    这表示公共动作上出现分歧时,发生死锁,记为并发结束。

  • L5A、L5B:(a→P) ∣∣ (c→Q)=a→(P ∣∣ (c→Q)),  (c→P) ∣∣ (b→Q)=b→((c→P) ∣∣ Q)(a \to P) \ || \ (c \to Q) = a \to (P \ || \ (c \to Q)), \ \ (c \to P) \ || \ (b \to Q) = b \to ((c \to P) \ || \ Q)(aP)  (cQ)=a(P  (cQ)),  (cP)  (bQ)=b((cP)  Q)
    这表示,一方要做私有动作,一方要做公共动作时,让私有动作的进程自己执行完私有动作。

  • L6:(a→P) ∣∣ (b→Q)=(a→(P ∣∣ (b→Q)) ∣ b→((a→P) ∣∣ Q))(a \to P) \ || \ (b \to Q) = (a \to (P \ || \ (b \to Q)) \ | \ b \to ((a \to P) \ || \ Q))(aP)  (bQ)=(a(P  (bQ))  b((aP)  Q))
    这表示,双方都要做私有动作时,私有动作都要各自执行完,但任选一顺序执行。如上式右侧的选择符号∣|左边是PPP先执行私有动作aaa的情况,右边是QQQ先执行私有动作bbb的情况。

1.3 死锁解除(free from deadlock)

如在哲学家就餐问题中,如果每个哲学家拿起自己左边的筷子,那么所有哲学家都没法就餐,因为都处于要去拿右边的筷子的状态,但是该资源已经被占用了。

死锁解除可以通过在原来的进程上并发一些约束进程来实现。如在哲学家就餐问题中,只要引入FOOTFOOTFOOT进程,可以理解成引导哲学家进餐的侍者(footman),其动作就是让哲学家坐下/起身。这两个动作是每个哲学家自己就有的,所以将这个进程并发上去,也就变成了“当有哲学家希望坐下/起身,需要侍者允许它这样做时才行”,也就是两个进程在公共事件上要同步推进。
⋃i=04{i.sits down, i.gets up}U=⋃i=04{i.gets up}D=⋃i=04{i.sits down} \bigcup_{i=0}^4 \{i.sits \ down, \ i.gets \ up\} \\ U = \bigcup_{i=0}^4 \{i.gets \ up\} \\ D = \bigcup_{i=0}^4 \{i.sits \ down\} i=04{i.sits down, i.gets up}U=i=04{i.gets up}D=i=04{i.sits down}
为了避免出现死锁,最多只允许桌上有4个哲学家即可,可以定义0~5一共5个相关的进程,来表示当前桌上有几个哲学家(将约束转换成进程的技巧)。所以FOOTFOOTFOOT进程的互递归定义如下:
FOOT0=(x:D→FOOT1)FOOTj=(x:D→FOOTj+1 ∣ y:U→FOOTj−1)     for j∈{1,2,3}FOOT4=(y:U→FOOT3) FOOT_0 = (x:D \to FOOT_1) \\ FOOT_j = (x:D \to FOOT_{j+1} \ | \ y:U \to FOOT_{j-1}) \ \ \ \ \ for \ j \in \{1,2,3\} \\ FOOT_4 = (y:U \to FOOT_3) FOOT0=(x:DFOOT1)FOOTj=(x:DFOOTj+1  y:UFOOTj1)     for j{1,2,3}FOOT4=(y:UFOOT3)
将其和原进程并发,即得到了解除死锁后的进程:
NEWCOLLEGE=(COLLEGE ∣∣ FOOT0)NEWCOLLEGE=(COLLEGE \ || \ FOOT_0)NEWCOLLEGE=(COLLEGE  FOOT0)
无死锁的进程可以这样表达,在执行过任何trace中的动作后都无法进入STOPSTOPSTOP状态:
(NEWCOLLEGE/s)≠STOP     for all s∈traces(NEWCOLLEGE)(NEWCOLLEGE/s) \neq STOP \ \ \ \ \ for \ all \ s \in traces(NEWCOLLEGE)(NEWCOLLEGE/s)=STOP     for all straces(NEWCOLLEGE)

2 相关操作

2.1 并发进程的符号变换(change of symbol)

fff是从原字母表αP\alpha PαP到新字母表AAA的映射:
f:αP→Af:\alpha P \to Af:αPA
该映射作用于进程PPP时,即将其中所有字母映射到新的字母表上去:
αf(P)=f(αP)\alpha f(P) = f(\alpha P)αf(P)=f(αP)
对变换后的进程取迹集合,相当于对原进程的所有迹求上节2.9的迹上符号变换:
traces(f(P))={f∗(s) ∣ s∈traces(P)}traces(f(P))=\{f^*(s) \ | \ s \in traces(P)\}traces(f(P))={f(s)  straces(P)}


进程的符号变换可用于模型修改,如需要修改贩卖机的商品价格,就可以用符号变换:
f(in2p)=in10pf(in1p)=in5p... f(in2p)=in10p \\ f(in1p)=in5p \\ ... f(in2p)=in10pf(in1p)=in5p...


进程的符号变换可用于模型结合,如要连接两个in-out的COPYBITCOPYBITCOPYBIT进程,使其能够按序工作,即从第一个COPYBITCOPYBITCOPYBIT输入,传给第二个COPYBITCOPYBITCOPYBIT最后再输出,可用符号变换将前者的输出和后者的输入连接起来:
f(out.0)=g(in.0)=mid.0f(out.1)=g(in.1)=mid.1... f(out.0)=g(in.0)=mid.0 \\ f(out.1)=g(in.1)=mid.1 \\ ... f(out.0)=g(in.0)=mid.0f(out.1)=g(in.1)=mid.1...
最后,将两者并发就可得到新模型:
CHAIN2=f(COPYBIT) ∣∣ g(COPYBIT) CHAIN2=f(COPYBIT) \ || \ g(COPYBIT) CHAIN2=f(COPYBIT)  g(COPYBIT)

2.2 并发进程打标签(process labelling)

进程打标签用于区分多个行为一致的进程,如两台VMSVMSVMS一左一右一起提供服务,即:
(left:VMS ∣∣ right:VMS)(left:VMS \ || \ right: VMS)(left:VMS  right:VMS)
如果不对其打标签,那么两个行为一致的进程,其所有动作都是共同动作,即:
VMS ∣∣ VMS=VMSVMS \ || \ VMS=VMSVMS  VMS=VMS
这是没有意义的。

进程打标签l:P=fl(P)l:P = f_l(P)l:P=fl(P),是对其中的所有动作打标签:
fl(x)=l.x      for all x in αPf_l(x) = l.x \ \ \ \ \ \ for \ all \ x \ in \ \alpha Pfl(x)=l.x      for all x in αP

2.3 并发进程满足规范(specification)

这里的规范可见上节2.16迹与产品规格的定义,使用符号satsatsat连接进程和规范。并发进程满足规范主要有以下两个性质:

  • L1:P sat S(tr) ∧ Q sat T(tr)=>(P∣∣Q) sat (S(tr↾αP)∧T(tr↾αQ))P \ sat \ S(tr) \ \wedge \ Q \ sat \ T(tr) => (P||Q) \ sat \ (S(tr \upharpoonright \alpha P) \wedge T(tr \upharpoonright \alpha Q))P sat S(tr)  Q sat T(tr)=>(PQ) sat (S(trαP)T(trαQ))
    注意,其中=>=>=>左侧的trtrtrPPPQQQ各自的trace,而右侧的trtrtr(P∣∣Q)(P||Q)(PQ)的trace。

  • L2:如果PPPQQQ都不会终止并且至多有一个公共事件,那么(P∣∣Q)(P||Q)(PQ)不会终止。
    试想如果有两个以上的公共事件,那么PPPQQQ各自拿出不同的公共事件要求并发时,就会死锁。而只有一个公共事件时便不会有这种情况。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐