目录
4.1 进程同步的概念
1.主要任务
使并发执行的诸进程之间能有效地共享资源和相互合作,从而使程序的执行具有可再现性。
进程间的制约关系。
间接相互制约关系(互斥关系) 进程互斥使用临界资源
直接相互制约关系(同步关系)进程间相互合作
临界资源
系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源或共享变量。
临界资源可能是硬件,也可能是软件:变量,数据,表格,队列等
访问临界资源的循环进程描述如下:
while(TRUE)
{ ENTRY SECTION //进入区
CRITICAL SECTION //临界区
EXIT SECTION //退出区
REMAINDER SECTION //剩余区
}
互斥的实质就是同步,或者说,互斥是同步的一种特殊形式。
2.同步机制应遵循的原则
(1) 空闲让进:当无进程处于临界区,应允许一个请求进入临界区的进程立即进入自己的临界区;
(2) 忙则等待:已有进程处于其临界区,其它试图进入临界区的进程必须等待;
(3) 有限等待:等待进入临界区的进程不能"死等";
(4) 让权等待:不能进入临界区的进程,应释放CPU(如转换到阻塞状态);
3.进程同步机制
(1)软件同步机制
(2)硬件同步机制
(3)信号量机制(P-V操作)
(4)管程机制
4.2 软件同步机制
软件同步机制-Peterson解决方案
共享变量:
int turn = 0;
turn == i ; //表示 Pi 能进入临界区j = 1-i;
boolean flag[2];
flag [0] = flag [1] = false; //初值flag [i] = true ; //表示Pi 准备进入临界区
进程 Pi:
do {
flag [i]:= true;
turn = j;
while (flag [j] and turn = j) ;临界区
flag [i] = false;
剩余区
} while (1);
满足三个需求;解决了两个进程的临界区问题。
4.3 硬件同步机制
关中断,Test-and-Set指令,Swap指令
缺点:不符合“让权等待”原则,浪费CPU时间;很难解决复杂的同步问题。
Test-and-Set指令实现互斥
boolean TestAndSet(Boolean *lock)
{boolean old = *lock;
*lock = TRUE;
return old;}
进程 Pi
do {
while (TestAndSet(lock)) ;
critical section
lock = FALSE;
remainder section
}
共享数据:
boolean lock = FALSE;
Swap指令实现互斥
原子地交换两个变量
void Swap(boolean *a, boolean *b) {
boolean temp = *a;
*a = *b;
*b = temp;
}
进程 Pi
do {
key = true;
do {
Swap(lock,key);
}while (key != false)
临界区
lock = false;
剩余区
}while(true)
共享数据 (初始化为 false):
boolean lock;
boolean waiting[n];
4.4 信号量机制
1965年,由荷兰学者迪科斯彻Dijkstra提出(P、V分别代表荷兰语的Proberen (test)和Verhogen (increment)),是一种卓有成效的进程同步机制。
信号量-软件解决方案:
- 保证两个或多个代码段不被并发调用
- 在进入关键代码段前,进程必须获取一个信号量,否则不能运行
- 执行完该关键代码段,必须释放信号量
- 信号量有值,为正说明它空闲,为负说明其忙碌
类型:整型信号量、记录型信号量、AND型信号量、信号量集
1. 整型信号量:
- 信号量S-整型变量
- 提供两个不可分割的[原子操作]访问信号量
wait和signal操作可描述为:
wait(S)
{ while(S≤0); /*do no-op*/
S--; }
signal(S):
{ S++; }
1、设置一个代表资源数目的整型变量S.value(资源信号量)
2、设置一链表L用于链接所有等待的进程,进程等待队列S.list
typedef struct{
int value;
struct process_control_block *list;
}semaphore;
wait 和 signal 操作(即P、V操作)可描述为:
wait(semaphores *S) { //请求一个单位的资源
S->value --; //资源减少一个
if (S->value<0) block(S->list) //进程自我阻塞
}
当 S.value<0 时,进程会立即将自己阻塞,因此解决了整型信号量的“忙等”问题,做到了“让权等待”。
signal(semaphores *S) //释放一个单位资源
{
S->value++; //资源增加一个
if (S->value<=0) wakeup(S->list); //唤醒等待队列中的一个进程
}
3.AND型信号量
- AND型信号量同步的基本思想:将进程在整个运行过程中需要的所有资源,一次性全部分配给进程,待进程使用完后再一起释放。
- 对若干个临界资源的分配,采用原子操作。
- 在wait(S)操作中增加了一个“AND”条件,故称之为AND同步,或同时wait(S)操作,即Swait(Simultaneous wait)。
Swait(S1,S2,…,Sn) {
while (TRUE) {
if (Si>=1 && … && Sn>=1) {
for (i =1;i<=n;i++) Si--;;
break;
}
else {
place the process in the waiting queue associated with the first Si found with Si<1,and set the program count of this process to the beginning of Swait operation
}
}
}
Ssignal(S1,S2,…,Sn) {
while (TRUE) {
for (i=1 ; i<=n;i++) {
Si++;
Remove all the process waiting in the queue associated with Si into the ready queue.
}
}
}
4.信号量集
在记录型信号量中,wait或signal仅能对某类临界资源进行一个单位的申请和释放,当需要对N个单位进行操作时,需要N次wait/signal操作,效率低下
扩充AND信号量:对进程所申请的所有资源以及每类资源不同的资源需求量,在一次P、V原语操作中完成申请或释放
进程对信号量Si的测试值是该资源的分配下限值ti,即要求Si≥ti,否则不予分配。一旦允许分配,进程对该资源的需求值为di,即表示资源占用量,进行Si= Si-di操作
信号量的应用
- 利用信号量实现进程互斥
- 利用信号量实现前趋关系
- 利用信号量实现进程同步
1. 用信号量实现进程互斥
.P、V操作必须成对出现:遗漏P(mutex)将不能保证互斥访问,遗漏V(mutex)将不能再使用临界资源之后将其释放(给其他等待的进程)
..mutex用于实现互斥,初值为1。
- 在进程P1中,用S1;signal(S);
- 在进程P2中,用wait(S);S2;
main(){
Semaphore a,b,c,d,e,f,g;
a.value=0;b.value=0;c.value=0;
d.value=0;e.value=0;f.value=0;g.value=0;
cobegin
{ S1;signal(a);signal(b); }
{ wait(a);S2;signal(c) ;signal(d);}
{ wait(b);S3;signal(e); }
{ wait(c);S4;signal(f); }
{ wait(d);S5;signal(g); }
{ wait(e);wait(f);wait(g);S6; }
corend
}
3.利用信号量实现进程同步
semaphores s=0; //主要用于传递消息
4.5 管程机制
1.为什么引入管程?
2.管程的定义:
一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操能同步进程和改变管程中的数据。
3.管程的语法:
Monitor monitor_name { /*管程名*/
share variable declarations; /*共享变量说明*/
cond declarations; /*条件变量说明*/
public: /*能被进程调用的过程*/
void P1(……) {……} /*对数据结构操作的过程*/
void P2(……) {……}
……
void (……) {……}
……
{ /*管程主体*/
initialization code; /*初始化代码*/
……
}
}
4.管程功能
互斥
由编译器完成
同步
例如:进程P调用signal操作唤醒进程Q后。
4.6 经典进程的同步问题
4.6.1 生产者—消费者问题
只要仓库未满,生产者就可以把产品放入仓库。只要仓库未空,消费者就可以从仓库中取走物品
4.6.2 哲学家进餐问题
4.6.3 读者——写者问题
4.6.7 关于信号量的讨论
信号量的使用:
信号量必须置一次且只能置一次初值,初值不能为负数
除了初始化,只能通过执行P、V操作来访问信号量
使用中存在的问题:
饥饿
死锁 :两个或多个进程无限期地等待一个事件的发生,而该事件正是由其中的一个等待进程引起的。
例如:S和Q是两个初值为1的信号量
P0 P1
P(S); P(Q);
P(Q); P(S);
M M
V(S); V(Q);
V(Q) V(S);
饥饿 :无限期地阻塞,进程可能永远无法从它等待的信号量队列中移去(只涉及一个进程)。
信号量的物理含义:
信号量同步的缺点:
4.7 Linux进程同步机制