描述:
有两个进程:一组生产者进程和一组消费者进程共享一个初始为空、固定大小为n的缓存(缓冲区)。生产者的工作是制造一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复; 同时,只有缓冲区不空时,消费者才能从中取出消息,一次消费一段数据(即将其从缓存中移出),否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
问题的核心是:
生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,他们也是同步关系。
解决思路:对于生产者,如果缓存是满的就去睡觉。消费者从缓存中取走数据后就叫醒生产者,让它再次将缓存填满。若消费者发现缓存是空的,就去睡觉了。下一轮中生产者将数据写入后就叫醒消费者。
不完善的解决方案会造成“死锁”,即两个进程都在“睡觉”等着对方来“唤醒”。
只有生产者和消费者两个进程,正好是这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步PV操作的位置。使用“进程间通信”,“信号标”semaphore就可以解决唤醒的问题:
方法:
我们使用了两个信号标:full 和 empty 。信号量mutex作为互斥信号量,它用于控制互斥访问缓冲池,互斥信号量初值为 1;信号量 full 用于记录当前缓冲池中“满”缓冲区数,初值为0。信号量 empty 用于记录当前缓冲池中“空”缓冲区数,初值为n。新的数据添加到缓存中后,full 在增加,而 empty 则减少。如果生产者试图在 empty 为0时减少其值,生产者就会被“催眠”。下一轮中有数据被消费掉时,empty就会增加,生产者就会被“唤醒”。
伪代码:
semaphore mutex=1; //临界区互斥信号量
semaphore empty=n; //空闲缓冲区
semaphore full=0; //缓冲区初始化为空
producer ()//生产者进程
{
while(1)
{
produce an item in nextp; //生产数据
P(empty); //获取空缓冲区单元
P(mutex); //进入临界区.
add nextp to buffer; //将数据放入缓冲区
V(mutex); //离开临界区,释放互斥信号量
V(full); //满缓冲区数加1
}
}
consumer ()//消费者进程
{
while(1)
{
P(full); //获取满缓冲区单元
P(mutex); // 进入临界区
remove an item from buffer; //从缓冲区中取出数据
V (mutex); //离开临界区,释放互斥信号量
V (empty) ; //空缓冲区数加1
consume the item; //消费数据
}
}
问题描述:
桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈就可向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。
问题分析:
(1)关系分析。这里的关系稍复杂一些,首先由每次只能向其中放入一只水果可知爸爸和妈妈是互斥关系。爸爸和女儿、妈妈和儿子是同步关系,而且这两对进程必须连起来,儿子和女儿之间没有互斥和同步关系,因为他们是选择条件执行,不可能并发。
(2)整理思路。这里有4个进程,实际上可以抽象为两个生产者和两个消费者被连接到大小为1的缓冲区上。
进程之间的关系:
(3)信号量设置。首先设置信号量plate为互斥信号量,表示是否允许向盘子放入水果,初值为1,表示允许放入,且只允许放入一个。信号量 apple表示盘子中是否有苹果,初值为0,表示盘子为空,不许取,若apple=l可以取。信号量orange表示盘子中是否有橘子,初值为0,表示盘子为空,不许取,若orange=l可以取。
伪代码:
semaphore plate=l, apple=0, orange=0;
dad() { //父亲进程
while (1) {
prepare an apple;
P(plate) ; //互斥向盘中取、放水果
put the apple on the plate; //向盘中放苹果
V(apple); //允许取苹果
}
}
mom() { // 母亲进程
while(1) {
prepare an orange;
P(plate); //互斥向盘中取、放水果
put the orange on the plate; //向盘中放橘子
V(orange); //允许取橘子
}
}
son(){ //儿子进程
while(1){
P(orange) ; //互斥向盘中取橘子
take an orange from the plate;
V(plate); //允许向盘中取、放水果
eat the orange;
}
}
daughter () { //女儿进程
while(1) {
P(apple); // 互斥向盘中取苹果
take an apple from the plate;
V(plate); //运行向盘中取、放水果
eat the apple;
}
}
问题描述:
某系统有三个吸烟者进程和一个经销商进程:
分析:
吸烟者问题是经典的进程同步问题,其特点在于信号量的设置,本题的关键问题是判断有几个临界资源。烟草、纸和火柴三种原料并不能简单地看成是三种临界资源,因为它们并不是以单独的形式被三个吸烟者进程所竞争,而是以固定的组合被三个进程所申请因此可以考虑如下设置:
int random; //存储随机数
semaphore offer1=0; //定义信号量对应烟草和纸组合的资源
semaphore offer2=0; //定义信号量对应烟草和胶水组合的资源
semaphore offer3=0; //定义信号量对应纸和胶水组合的资源
semaphore finish=0; //定义信号量表示抽烟是否完成
//供应者
while(1){
random = 任意一个整数随机数;
random=random% 3;
if(random==0)
V(offerl) ; //提供烟草和纸
else if(random==l)
V(offer2); //提供烟草和胶水
else
V(offer3) //提供纸和胶水
// 任意两种材料放在桌子上;
P(finish);
}
//拥有烟草者
while(1){
P (offer3);
// 拿纸和胶水,卷成烟,抽掉;
V(finish);
}
//拥有纸者
while(1){
P(offer2);
// 烟草和胶水,卷成烟,抽掉;
V(finish);
}
//拥有胶水者
while(1){
P(offer1);
// 拿烟草和纸,卷成烟,抽掉;
v(finish);
}
问题描述:
一个数据文件或记录可被多个进程共享。
只要求读文件的进程称为“Reader进程”,其它进程则称为“Writer进程”。
允许多个进程同时读一个共享对象,但不允许一个Writer进程和其他Reader进程或Writer进程同时访问共享对象
“读者—写者问题”是保证一个Writer进程必须与其他进程互斥地访问共享对象的同步问题。
①定义互斥信号量wmutex,实现写、写互斥和写、读互斥。
②定义整型变量Readcount表示正在读的进程数目。由于只要有一个Reader进程在读,便不允许Writer进程写,因此仅当Readcount=0,即无Reader进程在读时,Reader才需要执行Wait(wmutex)操作。若Wait(wmutex)操作成功,Reader进程便可去读,相应地,做Readcount+1操作。同理,仅当Reader进程在执行了Readcount减1操作后其值为0时,才需执行signal(wmutex)操作,以便让Write进程写。
③由于Readcount为多个读进程共享(修改),因此需要以互斥方式访问,为此,需要定义互斥信号量rmutex,保证读进程间互斥访问Readcount。
伪代码:(读者优先)
semaphore wmutex = 1;
semaphore rmutex = 1;
int readcount = 0;// 记录有几个读者
void reader()
{
while(true)
{
wait(rmutex); // rmutex是为了保护readcount共享变量
if(readcount == 0)
{
wait(wmutex);// 保证第一个读进程来,写进程就不能进行
}
readcount++;
signal(rmutex);
// 读的过程
wait(rmutex);
readcount--;
if(readcount == 0)
{
signal(wmutex); // 保证最后一个读进程读完,释放wmutex信号量
}
signal(rmutex);
}
}
void writer()
{
while(true)
{
wait(wmutex);
// 写的过程
signal(wmutex);
}
}
伪代码:(写者优先)
semaphore wmutex = 1;
semaphore rmutex = 1;
semaphore w=1;//用于实现写优先
int readcount = 0;// 记录有几个读者
void reader()
{
while(true)
{
wait(w);//写优先
wait(rmutex); // rmutex是为了保护readcount共享变量
if(readcount == 0)
{
wait(wmutex);// 保证第一个读进程来,写进程就不能进行
}
readcount++;
signal(rmutex);
signal(w);//写优先
// 读的过程
wait(rmutex);
readcount--;
if(readcount == 0)
{
signal(wmutex); // 保证最后一个读进程读完,释放wmutex信号量
}
signal(rmutex);
}
}
void writer()
{
while(true)
{
wait(w);//写优先
wait(wmutex);
// 写的过程
signal(wmutex);
signal(w);//写优先
}
}
问题描述:
五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在桌子上有五只碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐毕,放下筷子继续思考。
分析:
放在桌子上的筷子是临界资源,在一段时间内只允许一位哲学家使用,为了实现对筷子的互斥访问,可以用一个信号量表示筷子,由这五个信号量构成信号量数组。
伪代码:
semaphore chopstick[5] = {1,1,1,1,1};
while(true)
{
/*当哲学家饥饿时,总是先拿左边的筷子,再拿右边的筷子*/
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);
// 吃饭
/*当哲学家进餐完成后,总是先放下左边的筷子,再放下右边的筷子*/
signal(chopstick[i]);
signal(chopstick[(i+1)%5]);
}
上述的代码可以保证不会有两个相邻的哲学家同时进餐,但却可能引起死锁的情况。假如五位哲学家同时饥饿而都拿起的左边的筷子,就会使五个信号量chopstick都为0,当他们试图去拿右手边的筷子时,都将无筷子而陷入无限期的等待。
为避免死锁,可以使用以下三种策略:
策略一:至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。定义信号量count,只允许4个哲学家同时进餐,这样就能保证至少有一个哲学家可以就餐。
semaphore chopstick[5]={1,1,1,1,1};
semaphore count=4; // 设置一个count,最多有四个哲学家可以进来
void philosopher(int i)
{
while(true)
{
think();
wait(count); //请求进入房间进餐 当count为0时 不能允许哲学家再进来了
wait(chopstick[i]); //请求左手边的筷子
wait(chopstick[(i+1)%5]); //请求右手边的筷子
eat();
signal(chopstick[i]); //释放左手边的筷子
signal(chopstick[(i+1)%5]); //释放右手边的筷子
signal(count); //离开饭桌释放信号量
}
}
**策略二:**仅当哲学家的左右两支筷子都可用时,才允许他拿起筷子进餐。可以利用AND 型信号量机制实现,也可以利用信号量的保护机制实现。利用信号量的保护机制实现的思想是通过记录型信号量mutex对取左侧和右侧筷子的操作进行保护,使之成为一个原子操作,这样可以防止死锁的出现。描述如下:
semaphore mutex = 1; // 这个过程需要判断两根筷子是否可用,并保护起来
semaphore chopstick[5]={1,1,1,1,1};
void philosopher(int i)
{
while(true)
{
/* 这个过程中可能只能由一个人在吃饭,效率低下,有五只筷子,其实是可以达到两个人同时吃饭 */
think();
wait(mutex); // 保护信号量
wait(chopstick[(i+1)%5]); // 请求右手边的筷子
wait(chopstick[i]); // 请求左手边的筷子
signal(mutex); // 释放保护信号量
eat();
signal(chopstick[(i+1)%5]); // 释放右手边的筷子
signal(chopstick[i]); // 释放左手边的筷子
}
}
semaphore chopstick[5]={1,1,1,1,1};
do{
//think()
Swait(chopstick[(i+1)%5],chopstick[i]);
//eat()
Ssignal(chopstick[(i+1)%5],chopstick[i]);
}while(true)
**策略三:**规定奇数号的哲学家先拿起他左边的筷子,然后再去拿他右边的筷子;而偶数号的哲学家则先拿起他右边的筷子,然后再去拿他左边的筷子。按此规定,将是1、2号哲学家竞争1号筷子,3、4号哲学家竞争3号筷子。即五个哲学家都竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一个哲学家能获得两支筷子而进餐。
semaphore chopstick[5]={1,1,1,1,1};
void philosopher(int i)
{
while(true)
{
think();
if(i%2 == 0) //偶数哲学家,先右后左。
{
wait (chopstick[(i + 1)%5]) ;
wait (chopstick[i]) ;
eat();
signal (chopstick[(i + 1)%5]) ;
signal (chopstick[i]) ;
}
else //奇数哲学家,先左后右。
{
wait (chopstick[i]) ;
wait (chopstick[(i + 1)%5]) ;
eat();
signal (chopstick[i]) ;
signal (chopstick[(i + 1)%5]) ;
}
}
}