并发进程
前驱图
- 有向无环图,图中每个结点表示一个语句、一个计算步骤、或一个进程。
- 结点间的有向边表示偏序或前趋(precedence relation)关系“→” 。
- →={( P i P_i Pi, P j P_j Pj)| P j P_j Pj启动之前 P i P_i Pi必须已经完成}。
- ( P i P_i Pi, P j P_j Pj)∈→可记作 P i P_i Pi→ P j P_j Pj, 称 P i P_i Pi是 P j P_j Pj的前趋, P j P_j Pj是 P i P_i Pi的后继。
- 在前趋图中,没有前趋的结点称为初始结点,没有后继的结点称为终止结点。
- 每个结点可以有一个权重(weight),它可以表示该结点所包含的程序量或计算时间。
程序的顺序执行
- 内部顺序性:对于一个进程来说,它的所有指令是按序执行的。
- 外部顺序性:对于多个进程来说,所有进程的活动是依次执行的。
顺序程序特性
(1)连续性: 指令逐条执行
(2)封闭性: 不受其它程序及外界因素影响
(3)可再现性: 结果与推进速度无关
并发程序特性
(1)间断性:程序交叉执行。
(2)非封闭性:一个进程的运行环境可能被其它进程所改变,从而相互影响。
(3)不可再现性:由于交叉的随机性,并发程序的多次执行可能对应不同的交叉,因而不能期望重新运行的程序能够再现上次运行的结果。
程序并发执行的条件
在失去封闭性的条件下,保持可再现性
读集与写集
- R ( p i ) = a 1 , a 2 , … , a m R(p_i)={a_1,a_2,…,a_m} R(pi)=a1,a2,…,am表示程序 p i p_i pi在执行期间所需读取的所有变量的集合,称为“读集”;
- W ( p i ) = b 1 , b 2 , … , b n W(p_i)={b_1,b_2,…,b_n} W(pi)=b1,b2,…,bn表示程序 p i p_i pi在执行期间所需改变的所有变量的集合,称为“写集”。
Bernstein条件
若两个程序p1,p2满足如下条件,则能够保持可再现性,因而可以并发执行,该条件称为Bernstein条件。
R ( p 1 ) ∩ W ( p 2 ) ∪ R ( p 2 ) ∩ W ( p 1 ) ∪ W ( p 1 ) ∩ W ( p 2 ) = Φ R(p_1)∩W(p_2)∪R(p_2)∩W(p_1)∪W(p_1)∩W(p_2)=Φ R(p1)∩W(p2)∪R(p2)∩W(p1)∪W(p1)∩W(p2)=Φ
与时间有关的错误
举例:
- 就绪队列的整队问题
- 死锁
-
错误原因之1:
进程执行不正确的交叉(interleave); -
错误原因之2:
同时对一个公共变量(x)操作,其中一个进程的操作没有结束,另一个进程也对公共变量进行操作,使得公共变量处于一种不确定的状态,用数据库的术语说就是失去了变量x的数据完整性。 -
Remarks:
上述错误并不是一定发生,而是与进程的推进速度有关,速度是时间的函数,因为这类错误称为与时间相关的错误
某些交叉结果不正确;必须去掉导致不正确结果的交叉。
进程互斥
共享变量
多个进程都需要访问的变量。
临界区域
访问共享变量的程序段。把临界区与其所对应的共享变量联系起来称为关于某一种共享变量的临界区
共享变量和临界区域的表示
共享变量: shared <一组变量>
临界区域: region <一组变量> do <语句>
进程互斥的定义
多个进程不能同时进入关于同一组共享变量的临界区域,否则可能发生与时间有关的错误,这种现象称为进程互斥。
二层含义:
(1)任何时刻最多只能有一个进程处于同一组共享变量的相同的临界区域;
(2)任何时刻最多只能有一个进程处于同一组共享变量的不同的临界区域。
实现互斥的要求
- 互斥进入: 一次只允许一个进程进入关于同一组公共变量的临界区;
- 空闲让进: 临界区空闲时,放行一个进入者;
- 有限等待: 一个想要进入临界区的进程在等待有限个进程进入并离开临界区后获得进入临界区的机会
调度原则
- 当关于某一组共享变量的所有临界区均为空闲时,一个要求进入该组共享变量某一临界区的进程应该能够立即进入;
- 当关于某一组共享变量的某一临界区被占用时,一个要求进入该组共享变量某一临界区的进程应当等待;
- 当一个进程离开关于某一组共享变量的某一临界区时,应当容许某一个等待进入该组共享变量某一临界区的进程进入;
软件实现
- 完全用程序实现,不需特殊硬件指令支持。
- 可用于单CPU和多CPU环境中。
- 有忙式等待问题
petersom算法
flag表示是否想进入临界区,turn表示轮到谁了
两个进程互斥算法
boolean flag[2];
int turn;
P0:
Do{
flag[0]=true; turn=1;
while (flag[1] && turn==1);
临界区
flag[0]=false;
其余代码
}while(1);
P1:
Do{
flag[1]=true; turn=0;
while (flag[0] && turn==0);
临界区
flag[1]=false;
其余代码
}while(1);
Dekkel算法
flag表示是否想进入临界区,turn表示轮到谁了
两个进程互斥算法
int flag[2]; (init 0)
int turn; (0 or 1)
P0:
do{
flag[0]=1;
while(flag[1])
if(turn==1){
flag[0]=0;
while (turn==1)
skip;
flag[0]=1;
}
临界区
turn=1;
flag[0]=0;
其余代码
}while(1);
P0:
do{
flag[1]=1;
while(flag[0])
if(turn==0){
flag[1]=0;
while (turn==0)
skip;
flag[1]=1;
}
临界区
turn=0;
flag[1]=0;
其余代码
}while(1);
Lamport面包店算法
设置一个发号者,按0,1,2,…, 发号。想进入临界区的进程抓号,抓到号之后按由小到大的次序依次进入。
- Problem: 两个进程可能抓到相同的号。
Why? 为保证抓到不同的号,需要互斥机制。 - Resolution: 若抓到相同的号,按进程编号依次进入。
- Definition: (a,b)<(c,d) iff (a<c)or(a==c and b<d)
n个进程互斥算法
choosing为true表示正在抓好,否则表示未抓号或已经抓完。
number表示抓到的号
Boolean choosing[0,…,n-1];(false)
Int number[0,…,n-1]; (0)
Pi 进入:
1. choosing[i]=true;
2. number[i]=max{
number[0],…,number[n-1]}+1;
3. choosing[i]=false;
4. For(j=0;j<n;j++){
5. While (choosing[j]) skip;
6. While (number[j]!=0)and
7. (number[j],j)<(number[i],i) skip;
8. }
Eisenberg/Mcguire算法
n个进程互斥
enum flag[0,…,n-1] (idle, want_in, in_cs);
int turn; //0..n-1; 初始任意
flag[i]==idle: //进程Pi不想进入临界区
flag[i]==want_in: //进程Pi想进入临界区
flag[i]==in_cs: //进程Pi想进入或已进入临界区
Pi进入:
Do{
flag[i]=want_in;
j=turn;
While (j!=i)
If (flag[j] != idle) j=turn
Else j=(j+1)% n;
flag[i]=in_cs;
j=0;
While (j<n)and(j==i or flag[j]!=in_cs) do
j++;
}while (j!=n);
turn=i;
临界区
Pi离开:
j=(turn+1)% n;
While (flag[j]==idle)
j=(j+1)% n;
turn=j;
flag[i]=idle;