第六章 并发程序设计
知识要点
程序的顺序执行与并发执行。
进程互斥:临界区、临界资源、临界区管理的实现方法。
进程同步:采用同步机制(信号量)解决同步问题。
管程:概念、特性、结构、条件变量和实现。
通信机制分类和实现原理。
死锁:定义、引发原因、死锁必要条件、死锁处理方法。
顺序程序设计特点
程序执行的顺序性;程序环境的封闭性;执行结果的确定性(与执行速度无关);计算过程的可再现性。
进程的并发性
- 从宏观上看,一个时间段中几个进程都在同一处理器上。
- 从微观上看,任一时刻仅有一个进程在处理器上运行。
程序并发执行的特点
失去了程序的封闭性,程序执行的结果依赖于程序执行时的相对速度。
注:如果程序执行的结果是一个与是一个与时间无关的函数,则具有封闭性。
并发程序设计的特性
并行性;共享性;交往性(多个进程间存在制约)。
程序并行性的表示
1、有向图
2、并行语言:PASCAL语言
并发进程分类
1、无关的并发进程
判断无关进程的条件:Bernstein条件
2、交往的并发进程:一组并发进程共享某些变量。
进程互斥
1、定义:若干进程因相互争夺独占型资源时所产生的竞争制约关系。
2、同步与互斥的比较
同步 | 互斥 |
进程-进程(直接关系) | 进程-资源-进程(间接关系) |
时间次序上受到某种限制 | 竞争到某一物理资源时不允许进程工作 |
相互清楚对方的存在及作用,交换信息 | 不一定清楚其进程情况 |
往往指有几个进程共同完成一个任务 | 往往指多个任务多个进程间通讯制约 |
例:生产与消费之间,作者与读者之间 | 例:交通十字路口 |
临界区管理
1、定义:与共享变量有关的程序段叫“临界区”,共享变量代表的资源叫“临界资源”。
2、临界区调度原则
- 互斥使用,有空让进
- 忙则等待,有限等待
- 择一而入,算法可行
3、实现临界区管理的硬件设施
- 关中断:不适合多CPU,一个进程只能禁止本CPU的中断。
- 硬件指令方法
- 一条机器硬件指令完成读写两个操作,如TS指令和SWAP指令。
- 缺点:其他想进入临界段的进程必须不断地检测布尔变量lock的值,造成处理机的浪费,即“忙等待”;由于随机从等待队列中选取进程,会出现饥饿现象。
- 优点:适用于多处理器情况;可以被适用于多重临界段情况。
信号量与PV操作
信号量的物理意义:信号量S的出初值表示可用资源数,当S≤0时,表示已无资源可分配,其绝对值表示此时在等待队列中等待分配资源的进程数。
信号量的变化范围:设可用资源数为m,进程数为n,则 -(n-m) ≤ S ≤ m。
P(s):将信号量s减去1,若结果小于0,则调用P(s)的进程被置成等待信号量s的状态。
V(s):将信号量s加1,若结果小于等于0,则唤醒一个等待信号量s的进程。
生产者-消费者问题
1、一个生产者/消费者共享一个缓冲区。
semaphore empty;/*可以使用的空缓冲区数*/
semaphore full;/*缓冲区内可以使用的产品数*/
empty = 1;
full = 0;
cobegin
process producer
{
while(1){
生产产品;
P(empty);
往缓冲区放产品;
V(full);
}
}
process consumer
{
while(1){
P(full);
拿产品;
V(empty);
消费产品;
}
}
2、多个生产者-多个消费者共享多个缓冲区
资源:空的缓冲区数、满的缓冲区数、空仓头指针,产品链头指针。
semaphore empty; empty = k;/*可以使用的空缓冲区数*/
semaphore full; full = 0;/*缓冲区内可以使用的产品数*/
semaphore Mutex_P0; Mutex_P0 = 1;/*互斥信号量*/
semaphore Mutex_C0; Mutex_C0 = 1;/*互斥信号量*/
int in = 0;/*放入缓冲区的头指针*/
int out = 0;/*取出缓冲区的头指针*/
cobegin
process producer_i
{
while(1){
生产;
P(empty);
P(Mutex_P0);
放入产品;
in = (in + 1) % k;
V(Mutex_P0);
V(full);
}
}
process consumer_j
{
while(1){
P(full);
P(Mutex_C0);
取出产品;
out = (out + 1) % k;
V(Mutex_C0);
V(empty);
消费产品;
}
}
coend
苹果橘子问题
资源:苹果,橘子,空位。
int plate;
semaphore sp;/*盘子里可以放几个水果*/
semaphore sg1;/*盘子里有橘子*/
semaphore sg2;/*盘子里有苹果*/
sp = 1;/*盘子里允许放入一个水果*/
sg1 = 0;/*盘子里没有橘子*/
sg2 = 0;/*盘子里没有苹果*/
process father{
while(1){
L1:削一个苹果;
P(sp);
把苹果放入plate;
V(sg2);
}
}
process mother{
while(1){
L2:剥一个橘子;
P(sp);
把橘子放入plate;
V(sg1);
}
}
process son{
while(1){
L3:P(sg1);
从plate中取橘子;
V(sg);
吃橘子;
}
}
process daughter{
while(1){
L4:P(sg2);
从plate中取苹果;
V(sg);
吃苹果;
}
}
读者-写者问题
资源:阅读者计数器、BUF(缓冲区)。
阅读者计数器信号量的变化范围:[-(m-1),1]
BUF信号量的变化范围:[-n,1]
int readcount;
semaphore Mutex_ReadCount = 1;
semaphore Mutex_BUF = 1;
process reader_i()
{
while(1){
P(Mutex_ReadCount);/*阅读者计数器登记*/
readcount++;
if(readcount==1)/*第一个阅读者申请BUF*/
P(Mutex_BUF);
V(Mutex_ReadCount);
读文件;
P(Mutex_ReadCount);
readcount--;
if(readcount==0)/*最后一个阅读者释放BUF*/
V(Mutex_BUF);
V(Mutex_ReadCount);
}
}
process writer_i()
{
while(1){
P(Mutex_BUF);
写文件;
V(Mutex_BUF);
}
}
哲学家就餐问题
哲学家就餐问题避免死锁的办法:
- 至多允许四个哲学家同时吃。
- 奇数号先取左手边的叉子,偶数号先取右手边的叉子。
- 每个哲学家取到手边的两把叉子才吃,否则一把叉子也不取。
semaphore fork[5];
for(int i = 0;i < 5;i++)
fork[i] = 1;
cobegin
process philosopher_i()
{
while(1){
思考;
P(fork[i]);
P(fork[(i+1)%5]);
吃饭;
V(fork[i]);
V(fork[(i+1)%5];
}
}
coend
睡眠理发师问题
问题描述:理发店有一位理发师、一把理发椅和n把供等候理发的顾客坐的椅子。如果没有顾客,理发师便在理发椅上睡觉,一个顾客到来时,它必须叫醒理发师,如果理发师正在理发时又有顾客到来,则如果有空椅子可坐,就坐下来等待,否则就离开。
资源:理发师、顾客、修改顾客人数的互斥信号量。
int waiting = 0;
int CHAIRS = N;
semaphore customers,barbers,mutex;
customers = 0;barbers = 0;/*理发师在睡觉*/
mutex = 1;/*互斥信号量*/
cobegin
process barber()
{
while(1){
P(customers);/*有顾客吗?若无顾客,理发师睡眠*/
P(mutex)/*若有顾客,进入临界区*/
waiting--;
V(barbers);/*理发师准备为顾客理发*/
V(mutex);
理发;
}
}
process customer_i
{
P(mutex);/*进入临界区*/
if(waiting<CHAIRS){/*有空椅子吗?*/
waiting++;
V(customers);
V(mutex);/*退出临界区*/
P(barbers);
理发;
}
else
V(mutex);/*人满了,离开*/
}
coend
管程
1、引入目的:进程在使用过程中对信号量的操作分散在各个进程中,不易控制。
2、进程具有动态性,管程只是一个资源管理模块,供进程调用。
3、进程可以在任意时刻调用管程中的过程,管程在任一时刻只能有一个活跃进程。
4、管程中的条件变量:wait(类似P操作)、signal操作(类似V操作)。
进程通信
1、定义:进程之间的信息交换。
2、进程通信方式:
- 低级通信原语:交换信息量较少,如互斥、同步机制。
- 高级通信原语:交换信息量较多,如直接通信、间接通信。
直接通信 | 间接通信 | |
定义 | 一个进程直接发送消息给接收者进程 | 进程通过一个“信箱”来传递消息 |
发送消息 | 原语send(P,消息):把一个消息发送给进程P | 原语send(A,信件):把一封信件(消息)传送到信箱A |
接收消息 | 原语receive(Q,消息):从进程Q接收一个消息 | 原语receive(A,信件):从信箱A中接受一封信件(消息) |
3、消息队列:将消息组织成消息队列,用链指针链接起来,头指针放在进程的PCB中。
死锁及产生
1、定义:系统中的一组进程,由于竞争系统资源或由于彼此通信而永远阻塞,称这些进程处于死锁状态。
2、产生原因
- 进程竞争资源,而资源不足。(不可剥夺资源)
- 进程推进顺序不合适。
设系统某类资源有m个,有n个进程,每个进程需要k个该资源,则当满足nk ≤ m + (n - 1)时,系统不会引起死锁。
3、产生死锁的必要条件
- 互斥条件:一个资源依次只能被一个进程所使用。
- 不可抢占条件
- 部分分配条件:一个进程已占有分给它的资源,但仍要求其他资源。
- 循环等待条件:环形请求链。
进程-资源分配图
1、Pi → Rj为请求边,表示进程Pi申请资源类Rj中的一个资源。Rj → Pi 为分配边,表示Rj类中的一个资源已被进程Pi占用。
2、死锁的条件:进程-资源分配图中有环路且永远等待。
- 若每类资源只有一个,出现环路一定发生了死锁。
处理死锁的基本方法
1、死锁的预防:破坏死锁产生的必要条件
- 破坏互斥条件:资源可同时访问。
- 破坏不可抢占条件:采用剥夺式调度方法。
- 破坏部分分配条件:预先静态分配法。
- 在进程开始运行之前,一次分配给其所需的全部资源,若系统不能满足,则进程阻塞,直到系统满足其要求。
- 破坏循环等待条件:有序资源使用法。
- 进程对资源的请求必须严格按资源序号的递增次序申请,经常用的普通的资源低序号,贵重少用的高序号。
2、死锁的避免:在分配资源时判断是否会出现死锁,如不会死锁,则分配资源。
- 单资源银行家算法
- 计算所有客户离最大需求的距离。
- 检查系统资源状况,获得可以被满足的客户。
- 一般挑选距离最短的客户,假设满足该客户,则可以收回该客户所分配的所有资源。
- 计算剩余客户离最大需求的距离。
- 返回(2),如此反复下去。若所有投资最终都被收回,则该状态是安全的,最初的请求可以批准。
- 多资源银行家算法
3、死锁的检测和恢复
- 撤销进程并剥夺资源
- 使用挂起和解除挂起机构