进程同步概念
- 概念:并发进程在一些关键点上可能需要互相等待或互通消息,这种相互制约的等待或互通消息称为进程同步。
- 同步机制应遵循的准则
- 空闲让进:其他进程均不处于临界区;
- 忙则等待:已有进程处于其临界区;
- 有限等待:等待进入临界区的进程不能"死等";
- 让权等待:不能进入临界区的进程,应释放CPU(如转换到等待状态)
使用软件算法实现互斥
- 算法1:两个进程P0, P1共享某临界资源
- 算法一:设立一个公用整型变量 turn,描述允许进入临界区的进程标识,假设初始化turn=0,表示首先轮到P0访问临界资源
-
-
- 该算法,初始化是设置的等待 P0 对进程进行访问,但是如果 P1 先申请访问就不能执行,违背了空闲让进的准则。
- 当 turn 值为1时,如果 P0 进程想要访问临界资源,会在 while 语句中执行空循环等待,知道 turn 变为 0。所以当 P0 不能拿到访问权时,并没有放弃等待,而是一直占着 CPU 在做空循环等待,违背了让权等待的准则。
- 算法二:设立一个标志数组flag[2]:描述进程是否已在临界区,初值均为0(FALSE),表示进程都不在临界区。
-
-
-
- 在这个算法中,当 flag[1] = 0 的时候,我们可以进入 P0 进程,当 P0 进程刚执行完第一条 while 语句的时候,它的时间片到了, 中断 P0 进程的执行,进而执行 P1 的时候,又会因为 flag[0] = 0,认为临界资源可以访问,这样 P0 和 P1 都会同时访问临界资源。违背了忙则等待的原则
- 当 flag[1] 为 1 的时候,P0 进程的 while 语句会执行空循环等待,所以也违背了让权等待的原则。
-
使用锁机制实现互斥
- 锁的定义:用变量w代表某种资源的状态,w称为“锁” 。
- 上锁操作和开锁操作
- 上锁操作(临界资源的申请操作):
- 检测w的值 (是0还是1);
- 如果w的值为1,继续检测;
- 如果w的值为0,将锁位置1 (表示占用资源),进入临界区执行
- 开锁操作(临界资源的释放操作):
- 临界资源使用完毕,将锁位置0
- 上锁操作(临界资源的申请操作):
- 上锁原语和开锁原语
- 上锁原语
lock(){
test: if (w为1)
goto test; ∕* 测试锁位的值*∕
else w=1;
} ∕*上锁*∕
-
- 开锁原语
unlock(){
w=0;
} ∕*开锁*∕
- 操作方法
- 加锁操作 -》 执行临界区程序 -》 开锁操作
信号量机制
- 信号量数据结构的定义
typedef struct{
int value; /*信号量的值*/
PCB * L; /*进程等待队列队首指针*/
} semaphore ;
-
- value:初始化为一个非负整数值,表示空闲资源总数--若为非负值表示当前的空闲资源数,若为负值其绝对值表示当前等待临界资源的进程个数。
- L:初值为空
- 信号量的P、V操作: semaphore *S;
- P 操作(临界资源申请操作)
P(S){
S->value--;
if(S->value<0)
sleep(S->L); // 由于进程在不能申请到临界资源时是主动释放CPU并且阻塞到L链表上
// 所以信号量实现了让权等待的机制
}
-
- V 操作(临界资源释放操作)
V(S){
S->value++; // 归还一个临界资源给系统
if(S->value<=0)
wakeup(S->L); // 唤醒进程
}
利用信号量机制实现进程互斥与同步
- 实现互斥
- 为临界资源设置一个互斥信号量 mutex ,其初值为 1:
semaphore mutex=1
-
- 在每个进程中将临界区代码置于P(mutex)和V(mutex)原语之间:
-
- 算法描述如下:
-
- 给出一个例子:两个进程共享一台打印机
-
-
- 在执行 Pa 进程的过程中,P 操作先对 mutex 减一,得到 0。不是负数,可以直接执行。 Pb 进程如果在这个时候进行申请,此时执行 P 操作,mutex 得到 -1,会阻塞 Pb 进程。
- 当执行 Pa 进程的 V 操作时, mutex 值为 -1,加 1 得到 0。小于等于 0,唤醒等待进程 Pb。
- 当 Pb 进程执行到 V 操作时, mutex 加 1 得到 1,此时打印机空闲。
-
- 实现同步:进程间合作的前驱后继关系
- 为一个同步关系设置一个同步信号量S,其初值为0:
semaphore S=0 // 表示令牌
-
- 前驱进程完成操作后执行V(S):表示释放令牌;后继进程开始操作前执行P(S):表示申请令牌
- 算法描述:Pa -> Pb
-
- 举个例子: I -> C -> P
-
- 再举一个例子:对给出的前驱图编写同步信号量程序:
a,b,c,d,e,f,g:semaphore=0,0,0,0,0,0,0
cobegin
s1: { s1; v(a); v(b);}
s2: { p(a); s2; v(c); v(d);}
s3: { p(b); s3; v(e); }
s4: { p(c); s4; v(f); }
s5: { p(d); s5; v(g); }
s6: { p(e); p(f); p(g); s6; }
coend
-
- 最后,再给出一个司机和售票员的例子,司机和售票员的工作进程如下:
-
-
- 我们,首先需要寻找这两个进程之间的同步关系,得到同步关系如下:
- 售票员关闭车门→ 司机启动车辆
- 司机到站停车→ 售票员打开车门
- 对于这连个同步关系,我们设置相应的同步信号量如下:
- semaphore drive_sem={0,NULL};
- semaphore conductor_sem={0,NULL};
- 最后,我们根据这些信号量的设置,写出相应的算法如下:
- 我们,首先需要寻找这两个进程之间的同步关系,得到同步关系如下:
-
- 使用信号量解决进程同步问题的步骤
- 确定进程:
- 包括进程的数量、进程的工作内容。
- 确定进程同步互斥关系:
- 根据进程间是竞争临界资源还是相互合作处理上的前后关 系,来确定进程间是互斥还是同步。
- 确定信号量:
- 根据进程间的同步互斥关系确定信号量个数、含义、初始值,各进程需要对信号量进行的PV操作。
- 用类程序语言描述算法。
- 确定进程: