1. 生产者—消费者问题
利用记录型信号量解决生产者—消费者问题
这类问题就是两个进程对一个临界资源的使用的具体例子。
假定在生产者和消费者之间的共用缓冲池(就像是仓库),共有n个缓冲区(n个位置)。缓冲池未满生产者可以将消息放入缓冲池;缓冲池不为空消费者可以将消息取走。
可以利用互斥信号量mutex实现对缓冲池的互斥使用。
利用记录型信号量empty和full分别标识缓冲池中空的缓冲区和满的缓冲区。
可见:
①生产者和消费者对于仓库counter的使用是互斥的(竞争)。
②而生产者和消费者之间又相互制约(协作)。就是必须生产者生产了消费者才可以使用,反之.
解决方法:
设置互斥信号量M实现两个进程对缓冲池的互斥使用。
设置资源信号量E和F表示缓冲池中空的缓冲区和满的缓冲区数量。
初始值M=1,E=n,F=0。(表示刚开始缓冲池中缓冲区全为空)
对于生产者来说,首先要P(E),若是E为0,表示空的缓存区数量为0,则进入阻塞,必须等待消费者消费才能进入。
对于消费者来说,首先要P(F),若是F为0,表示满的缓存区数量为0,则进入阻塞,必须等待生产者生产才能进入。
同时,二者P(E/F)通过后,无论是谁先进入P(M),另一个都得阻塞。实现了对缓冲池的互斥使用。
注意:
互斥使用时,P(M)和V(M)在同一个进程内成对使用。
而协作使用,P(E/F)和V(E/F)分别在互相协作的两对进程内成对使用。
2. 哲学家进餐问题
这类问题是多个进程对多个临界资源的使用的实例。
可知,筷子是临界资源,在同一时间内只能一个人使用。
为了实现对筷子的互斥使用,可以用一个信号量表示一个筷子,由五个信号量构成信号量数组:
Var chopstick: array[0,…,4] of semaphore;信号量数组,表示五双筷子。
所有信号量均被初始化为1那么,第i位哲学家的活动可描述为:
repeat
wait(chopstick[i]); 等待第i个筷子,即左边筷子 P(i)
wait(chopstick[(i+1)mod 5]); 等待第i+1个筷子,即右边筷子 P(i+1)
eat; 吃饭,使用临界资源
signal(chopstick[i]); 释放第i个筷子,即左边筷子 V(i)
signal(chopstick[(i+1)mod 5]); 释放第i+1个筷子,即右边筷子 V(i+1)
think; 思考
until false;
当哲学家饥饿时,总是先去拿他左边的筷子,即执行wait(chopstick[i]); 成功后,再去拿他右边的筷子,即执行 wait(chopstick[(i+1)mod 5]);又成功后便可进餐。进餐完毕,又先放下他左边的筷子,然后再放右边的筷子。
这样虽然不会出现两个哲学家同时吃饭,但是可能会引起死锁:
假如五位哲学家同时饥饿而各自拿起左边的筷子时,就会使五个信号量chopstick 均为0; 当他们再试图去拿右边的筷子时,都将因无筷子可拿而无限期地等待。
解决方法:
①至多只允许四位哲学家同时拿左边的筷子,即至少保证有一位哲学家可以吃饭,在他吃完之后释放两双筷子,使得其他哲学家也可以吃饭。
②仅当哲学家左右两双筷子都空闲,才允许拿起筷子吃饭。即AND型信号量。
③规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子, 而偶数号哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子; 3、4号哲学家竞争3号筷子。即五位哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一位哲学家能获得两只筷子而进餐。
方法③实现起来很复杂,且不实用。
方法①即使用记录型信号量解决哲学家进餐问题:
方法②即使用AND信号量解决哲学家进餐问题:
(1)利用记录型信号量解决哲学家进餐问题
5支筷子是临界资源,用5个信号量表示S[5],所有信号量初始化为1, 表示一开始所有筷子可用;
设置记录型信号量N表示允许同时进餐人数,其初始值为4,那么第i个哲学家(i=0,1,...,4)的过程可以表示为:
定义信号量: N=4,S[5]={1,1,1,1,1}
while( 1 ) {
P( N )
P( S[i] ) // 拿左边的筷子
P( S[(i+1)%5] ) // 拿右边的筷子
进餐...
V( S[i] ) // 释放左边筷子
V( S[(i+1)%5] ) // 释放右边筷子
V( N )
思考...
}
相较于开始时出现死锁的解决方法,这里添加了一个记录型信号量N,放在最开始,当N等于0表示已经有4个哲学家拿起了左边筷子,第五个哲学家就得阻塞,等待有哲学家吃完,释放N。
(2)利用AND信号量机制解决哲学家进餐问题
Var chopsiick array of semaphore:=(1,1,1,1,1);
processi
repeat
think; 思考
Sswait(chopstick[(i+1)mod 5],chopstick[i]); 等待左右两双筷子
eat; 吃
Ssignat(chopstick[(i+1)mod 5],chopstick[i]); 释放左右两双筷子
until false;
3. 读者写者问题
这类问题是一个临界资源被两类进程使用,这两类之间互斥,但是每一类自身不互斥使用的实例。
读者/写者问题的关键:
多个读者进程可以共存
一个写进程和其他读/写进程不能共存
读者/写者问题的实例:
数据库
ftp 服务器
......
利用信号量解决读者—写者问题
为实现Reader与Writer进程间在读或写时的互斥而设置了一个互斥信号量w。
再设置一个整型变量n表示正在读的进程数目。
由于只要有一个Reader进程在读,便不允许Writer进程去写。因此,仅当n=0,表示尚无Reader进程在读时,Reader进程才需要执行 P(w)操作。
若P(w)操作成功,Reader进程便可去读,相应地,做Readcount+1操作。
仅当Reader进程在执行了Readcount减1操作后其值为0时,才须执行V(w)操作,以便让Writer进程写。
又因为 n 是一个可被多个Reader进程访问的临界资源,因此,也应该为它设置一个互斥信号量 s 。
信号量定义:
W=1, S=1 //W用来让读者进程和写者进程互斥访问临界资源,S用来让读者进程之间互斥访问临界资源正在读的读者数量 n。
整形变量 n=0 // 记录读者进程数量
读者进程: 写进程:
P( S ) P( W )
if( n==0 ) 写操作
p( W ) V( W )
n = n+1
V( S )
执行读操作
P( S )
n = n-1
if( n==0 )
V( W )
V( S )
可知,当读者进程运行时,首先应P(S),互斥访问临界资源 n ,且仅当n=0时,进行P(W),表示读者数量不为0了,此时写者进程只能阻塞,然后V(S),允许其他读者进程进入并修改临界资源n。
4. 理发师问题
这个问题是生产者/消费者,读者/写者问题的混合是一个综合问题
理发师问题描述如下:
角色有两个,理发师和顾客。
只有一个理发师和一把理发椅子,另有N把椅子供顾客休息等待。
没有顾客时,理发师休息,等待顾客。
每个顾客来时,先看是否有椅子空位,有空位,坐下等待;否则不等待直接走。
信号量定义:
X=0 Y=0 X用来表示理发师是否休息,Y表理发师是否正在理发
S=1 访问临界资源count
等待顾客数:
int count;
注意:
理发师是一个循环
而顾客是多个,但每一个只执行一次,不循环
关于count--,也可以考虑放入顾客进程中
5. 管程机制
系统中的各种硬件资源和软件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对该资源所执行的操作来表征该资源,而忽略了它们的内部结构和实现细节。
利用共享数据结构抽象地表示系统中的共享资源,而把对该共享数据结构实施的操作定义为一组过程,如资源的请求和释放过程request和 release。
进程对共享资源的申请、释放和其它操作,都是通过这组过程对共享数据结构的操作来实现的,这组过程还可以根据资源的情况,或接受或阻塞进程的访问,确保每次仅有一个进程使用临界资源,这样就可以统一管理对共享资源的所有访问,实现进程互斥。
代表共享资源的数据结构,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序,共同构成了一个操作系统的资源管理模块,我们称之为管程。
管程被请求和释放资源的进程所调用。 Hansan为管程所下的定义是:“一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据” 。
管程由四部分组成:
①管程的名称
②局部于管程内部的共享数据结构说明
③对该数据结构进行操作的一组过程
④对局部于管程内部的共享数据设置初始值的语句
例如:
如果用管程来管理筷子,那么哲学家进餐问题就可以简单的写为:
dp.pickup(i); 申请第i位置吃饭(该位置的两个筷子)
eat
dp.putdown(i);
think
这也是目前大多数系统中,对系统资源的一种处理方式,有系统统一处理资源使用者的同步问题。
6. Windows 提供的同步操作(或者信号量机制)
包括两类:
①API 方式
除前面介绍过的“临界区”变量,还提供下面 4 种同步对象:
Type Description
Event 事件
Mutex 互斥量
Semaphore 信号量
Waitable timer 专门时钟
然后需要专门的 Wait 函数来实现以上同步对象的操作。
API 方式在实际使用中比较麻烦,建议使用 MFC 方式。
②MFC 形式
包括以下四个同步类对象:
1) CSemaphore
2) CMutex
3) CCriticalSection
4) CEvent
以及两个操作这些同步对象的类:
1) CSingleLock //
2) CMultiLock // 实现 AND 操作