进程同步的基本概念
一、两种形式的制约关系
- 间接相互制约关系:同处于一个系统中的进程必然共享某种资源,如CPU、I/O设备等,间接相互制约即源于资源共享。又称为“互斥”
- 直接相互制约关系:同一个任务的多个进程,这种制约源于进程之间的合作关系。又称为"同步“
- 在并发程序执行下,由于存在上面两类相互制约关系,进行在运行过程中是否能得到处理机与执行的速度并不能由进程自身控制(异步性)会产生对共享变量或共享资源不正确的访问次序,造成“与时间有关的错误”,所以必须对进程的执行进行协调,保证结果的正确。
二、临界资源和临界区
- 临界资源:不能同时被两个进程同时访问的资源
- 临界区:每个进程访问临界资源的那段代码就是临界区
三、同步机制应该遵循的规则
- 空闲让进。当无进程处于临界区时,应允许一个进程进入临界区
- 忙则等待。当已有进程进入临界区时,其他进程必须等待
- 有限等待。对要求访问临界资源的进程,应保证在有限时间内进入自己的临界区,防止"死等“
- 让权等待。当进程不能进入自己的临界区时,应立即释放处理机,防止"忙等"
信号量机制
一、整型信号量
- 最初由Dijkstra把整型信号量定义为一个用于表示资源数目的整型量S,它与一般整形量不同,除初始化外,仅能通过两个标准的原子操作wait(S)和signal(S)来访问。这两个操作一直被分别称为P、V操作。 wait和signal操作可描述为:
wait(S){ //等待资源的调用
while (S≤0) ; //如果资源的数量少于0,就一直在等待
S=S-1;//如果s是大于0的,就-1,说明能用的资源又少一个
}
signal(S){ //释放资源
S=S+1; //可用资源可以多一个
}
- 缺点:当信号量S≤0的时候一直“忙等”,没有遵循“让权等待”
二、记录型信号量(资源信号量)
特点:每个进程都只是需要一个资源就可以了
- 采取了“让权等待”的策略,是一种不存在“忙等”现象的进程同步机制。
- 在采取了“让权等待”的策略后,又会出现多个进程等待访问同一临界资源的情况。为此,在记录型信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表L,用于链接上述的所有等待进程。
wait和signal操作可描述为:
typedef struct{
int value;
struct process_control_block* list;
} semaphore
wait(semaphore * S){//等待一个资源
S->value--;//资源数量少于一
if(S->value<0) //如果资源的数量少于0
block(S->list)//封锁资源,避免忙等
}
signal(semaphore *S){//释放一个资源
S->value++;//可用资源释放一个
if(S->value<=0) //如果可用资源的数量少于0
wakeup(S->list);//使用源语唤醒一个堵塞进程
}
说明: 1、对信号量S.value 每次wait操作, S.value:=S.value-1 S.value>0时,有该类资源
S.value<0时,该类资源已分配完毕,|S.value|=该信号量链表中已阻塞进程的数目
2、对信号量的每次signal操作,S.value:=S.value+1
若加1后仍是S.value≤0,表示在该信号量链表中仍有等待该资源进程被阻塞,则应调用wakeup原语,将S.L链表中的第一个等待进程唤醒。
3、若S.value的初值为1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量
三、AND信号量
特点:一个进程需要多个资源才能执行
- 在有些任务中,一个进程先要获得多个共享资源后才能执行,若进程A和B都要访问共享数据D和E,设信号量Dmutex和Emutex,并令他们的初值均为1。在两个进程中都要包含两个对Dmutex和Emutex的操作, 即
process A: | process B: |
---|---|
wait(Dmutex); | wait(Emutex); |
wait(Emutex); | wait(Dmutex); |
若进程A和B按下述次序交替执行wait操作:
process A: wait(Dmutex); 于是Dmutex=0
process B: wait(Emutex); 于是Emutex=0
process A: wait(Emutex); 于是Emutex=-1 A阻塞
process B: wait(Dmutex); 于是Dmutex=-1 B阻塞
- 在这种情况下,如果没有外力作用,AB进程都无法解脱,这种状态也叫“死锁”
解决的办法:一次性全部分配资源给进程,等待资源使用完后再一起释放,为此,在wait操作中,增加了一个“AND”条件,故称为AND同步,或称为同时wait操作, 即Swait(Simultaneous wait)
Swait(S1, S2, …, Sn)
{
While(TRUE)
{
if Si≥1 && … && Sn≥1{ // 每个资源都可用
for (i: =1; i<=n; i++)
Si:=Si-1; // 分配所有资源
break;
}
else
{ //否则,将进程放到等待资源Si的队列中}
//-----------------------------------------------------------------------
Ssignal(S1, S2, …, Sn){
while(TRUE){
for (i=1;i< = n; i++){
Si=Si+1; //释放所有资源
}
}
}
信号量的应用
四、用信号量集来实现进程互斥
- 在记录型信号量机制中,wait(S)和signal(S)操作仅能对信号量施以加1或减1操作,意味着每次只能获得或释放一个单位的临界资源,效率较低
- 在有些情况下,当资源数量低于某下限值时便不予分配。因而,在每次分配之前,都必须测试该资源的数量,看其是否大于等于下限值
- 在对AND型信号量机制扩充的基础上,形成一般化的“信号量集”机制
一般都有下面的情况:
1、Swait(S, d, d)。 此时在信号量集中只有一个信号量S, 但允许它每次申请d个资源,当现有资源数少于d时,不予分配
2、Swait(S, 1, 1)。 此时的信号量集已蜕化为一般的记录型信号量(S>1时)或互斥信号量(S=1时)
3、Swait(S, 1, 0)。这是一种很特殊且很有用的信号量操作。当S≥1时,允许多个进程进入某特定区;当S变为0后,将阻止任何进程进入特定区。换言之,它相当于一个可控开关
- 为使多个进程能互斥地访问某临界资源,只需为该资源设置一互斥信号量mutex,并设其初始值为1,取值范围(-1,0,1),然后将各进程访问该资源的临界区CS置于wait(mutex)和signal(mutex)操作之间即可。
1、mutex=,1,两个进程都没有进入互斥的临界区
2、mutex=0,有一个进程进入临界区运行,另一个进程必须等待
3、 mutex=-1,有一个进程在临界区运行,有一个进程因为等待而阻塞在信号量队列中,需要被当前已在临界区运行的进程退出的时候唤醒,所以这种情况下,其他进程是不可以进入到等待队列
Semaphore mutex =1;
PA(){
while(1){
wait(mutex);
临界区
Signal(mutex)
剩余区;
}
}
五、用信号量实现前趋关系
- P1中有语句S1;P2中有语句S2。我们希望在S1执行后再执行S2。为实现这种前趋关系,只需使进程P1和P2共享一个公用信号量S,并赋予其初值为0,将signal(S)操作放在语句S1后面,而在S2语句前面插入wait(S)操作,即
在进程P1中,用 S1;signal(S);
在进程P2中,用 wait(S);S2;
六、管程机制(有点类似面向对象中的类)
- 问题:每个要访问临界资源的进程都要自备同步操作,这样会给管理带来麻烦
- 解决:我们把这种同步操作抽象成一个类似于类的程序,每次想要调用就可以直接调用