进程经典问题总结(四)
以下出现代码均为伪代码;
2.5.1 生产者-消费者问题(考试模型占比 60%)
分析上图:缓冲区存在互斥问题,生产者与消费者之间还存在同步问题
1. 利用记录型信号量解决生产者-消费者问题(背下来)
假定在生产者和消费者之间的共用缓冲池中具有 n 个缓冲区,这时可以利用互斥信号量 mutex(初始值 1 )实现进程对缓冲池的互斥作用;利用信号量 empty(初始值为n)和 full(初始值为0)分别表示缓冲池中空缓冲区和满缓冲区的数量。
又假定这些生产者和消费者相互等效,只要总缓冲区未满,生产者便可将消息送入缓冲池中,只要缓冲池没空,消费者便可从缓冲池中取走一个消息
semaphore mutex =1; //缓冲区互斥操作的互斥信号量
semaphore empty = n; //空缓冲区数目,类型为资源信号量
semaphore full = 0; //满缓冲区为数目,类型为资源信号量
item buffer[n]
int in =0,out =0;
producer() //生产者进程
{
while(1)
{
produce an item nextp;
P(empty); //empty - 1,资源信号量又有同步作用
P(mutex); //加锁————>当多个生产者的时候要互斥访问
buffer[in] = nextp;
in = (in+1)%n; //循环队列
V(mutex); //解锁
V(full); //full + 1
}
}
consumer() //消费者进程
{
while(1)
{
P(full); //full - 1
P(mutex); //加锁————>当多个消费者的时候要互斥访问
nextc = buffer[out];
out = (out+1)%n; //循环队列
V(mutex); //解锁
V(empty); //full + 1
consumer the item in nextc;
}
}
注:
(1)empty + full = n ;
(2)若多个 P 操作颠倒,可能会存在死锁问题。
2.利用AND信号量解决生产者-消费者问题 (了解就行)
对于生产者-消费者问题,也可以利用AND信号量来解决,即:
- 用 Swait(empty,mutex) 来代替 wait(empty) 和 wait(mutex)
- 用 Ssignal(mutex,full) 来代替 signal(mutex) 和 signal(full)
- 用 Swait(full,mutex) 来代替 wait(full) 和 wait(mutex)
- 用 Ssignal(mutex,empty) 来代替 signal(mutex) 和 signal(empty)
semaphore mutex =1; //缓冲区互斥操作的互斥信号量
semaphore empty = n; //空缓冲区数目,类型为资源信号量
semaphore full = 0; //满缓冲区为数目,类型为资源信号量
item buffer[n]
int in =0,out =0;
producer() //生产者进程
{
while(1)
{
produce an item nextp;
Swait(empty,mutex)
buffer[in] = nextp;
in = (in+1)%n; //循环队列
Ssignal(empty,mutex); //解锁
}
}
consumer() //消费者进程
{
while(1)
{
Swait(full,mutex)
nextc = buffer[out];
out = (out+1)%n; //循环队列
Ssignal(empty,mutex)
consumer the item in nextc;
}
}
3. 利用管程来解决生产者-消费者问题(自主命题)
建立一个管程,并命名为producerconsumer,或简称PC。其中包括两个过程:
(1)put(x)过程。生产者利用该过程将自己生产的产品放到缓冲池中,并用整型变量count来表示在缓冲池中已有的产品数目,当count >= n 时,表示缓冲池已满,生产者等待。(也就意味需要有2个条件变量)
(2)get(x)过程。消费者利用该过程从缓冲池中取出一个产品,当count <= 0 时,表示缓冲池中已经无可取用的产品,消费者等待。
对于条件变量notfull 和 notempty,分别有两个过程cwait 和 csignal对它们进行操作:
(1)cwait(condition)过程:当管程被一个进程占用时,其他进程调用该过程时阻塞,并挂在条件condition 的队列上
(2)csignal(condition)过程:唤醒在cwait执行后阻塞在条件 condition 队列上的进程,如果这样的进程不止一个,则选择其中一个实施唤醒操作;如果队列为空,则无操作而返回
monitor producer-consumer //类似于一个类
{
int in=0,out=0,count=0;
itm buffer[n];
condition notfull,notempty;
void put(item nextp)
{
if count >= n then
cwait (notfull)
buffer [in]=nextp;
in= (in+1)%n;
count =count +1;
csignal(notempty);
}
void get(item *x)
{
if count <= 0 then
cwait (notempty)
*x=buffer[out];
out= (out+1)%n;
count =count - 1;
csignal(notfull); //告诉生产者你可以生产了
}
}PC;
//利用管程解决生产者-消费者问题
process producer()
{
item x;
while (TRUE)
{
produce an item in x;
PC.put(item); //相当于调用方法
}
}
process consumer()
{
item x;
while (TRUE)
{
PC.get(item);
consumer an item in x;
}
}
2.5.2 哲学家进餐问题(考试模型占比 10%)
5个哲学家坐在桌子边,桌上有 5 个碗和 5 只筷子,哲学家的生活方式是交替的进行思考和进餐,哲学家饥饿时便拿起两边的筷子进餐,但只有当拿到两只筷子后才能进餐,用餐完毕放下筷子,继续思考。
小提取:
- 思考问题
- 饿了停止思考,左手拿一只筷子(如果左侧哲学家已经持有它,需要等待)
- 右手拿一只筷子(如果右侧哲学家已经持有它,需要等待)
- 进餐
- 放右手筷子
- 放左手筷子
- 重新回到思考问题状态
1. 利用记录型信号量解决哲学家进餐问题(没有你先我后的同步问题)
筷子对应临界资源,在一段时间内只允许一位哲学家使用。为实现筷子的互斥使用,可以用一个信号量表示一只筷子,由这五个信号量构成信号量数组。————> semaphore chopstick[5];
方法一:互斥吃饭
至多只允许四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并且吃完后,释放两只筷子
semaphore chopstick[5]={1,1,1,1,1};
semaphore eating = 4; //仅允许四个哲学家可以进餐
void philosopher(int i) // 第 i 位哲学家的活动可以描述为
{
while( 1 )
{
P(eating); //请求就餐,若是第五个则先挨饿
P(chopstick[i]); //画个圈你就知道,这是他左手边的筷子
P(chopstick[i+1] % 5);
eating(); //进餐
V(chopstick[i+1] % 5); //释放右边筷子
V(chopstick[i]); //释放左边
V(eating); //释放信号量给其他哲学家
}
}
方法二:互斥拿筷子的动作
仅当哲学家的左、右两只筷子均可用的时候,才可以进餐
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex = 1; //设置取筷子的信号量
void philosopher(int i) // 第 i 位哲学家的活动可以描述为
{
while( 1 )
{
thinking();
P(mutex); //在取筷子前获得互斥量
P(chopstick[i]); //画个圈你就知道,这是他左手边的筷子
P(chopstick[i+1] % 5);
V(mutex); //释放互斥量
eating(); //进餐
V(chopstick[i+1] % 5); //释放右边筷子
V(chopstick[i]); //释放左边
}
}
方法三:互斥拿筷子顺序(奇偶数)
规定奇数号(1,3,5)哲学家先拿起左边筷子,然后再去拿右边筷子,而偶数(2,4)哲学家则相反,即五位哲学家先竞争奇数筷子,获得后再去竞争偶数筷子,最后总会由一位哲学家能过获得两只筷子而就餐,下图是哲学家抢奇数筷子图示。
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex = 1; //设置取筷子的信号量
void philosopher(int i) // 第 i 位哲学家的活动可以描述为
{
while( 1 )
{
thinking();
if (i%2==0) //偶数哲学家,先右后左
{
P(chopstick[i+1] % 5);
P(chopstick[i]);
eating(); //进餐
V(chopstick[i+1] % 5); //释放右边筷子
V(chopstick[i]);
}
else
{
P(chopstick[i]);
P(chopstick[i+1] % 5);
eating(); //进餐
V(chopstick[i]);
V(chopstick[i+1] % 5); //释放右边筷子
}
}
}
2. 利用 AND 信号量机制解决哲学家进餐问题
要求每个哲学家先获得两个临界资源(筷子)后方能进餐,本质就是 AND 同步问题,故用 AND 信号量机制解决哲学家进餐问题是最简洁的解法
semaphore chopstick[5]={1,1,1,1,1};
process(i)
{
while(1)
{
think;
Swait(chopstick[i+1] % 5,chopstick[i]); //把两个筷子全拿起来
eat;
Ssignal(chopstick[i+1] % 5,chopstick[i]);
}
}
2.5.3 读者 - 写者问题(考试模型占比 30%)
注:读者变形(自主命题)
- 公平队列
- 写者优先问题
一个数据文件或记录可被多个进程共享,只要求读该文件的进程称为 “Reader 进程”,其他进程为 “Writer 进程”,允许多个进程同时读一个共享对象,因为读不会使数据文件混乱,不允许一个 Writer 进程和其他Reader 进程或 Writer 进程同时访问一个对象。
读者 - 写者问题:是保证一个 Writer 进程必须与其他进程互斥访问共享对象的同步问题。
1. 利用记录型信号量解决 读者 - 写者 问题(读者优先)
&emsp: 为实现 Reader 和 Writer 进程间在读或写时的互斥而设置了一个互斥信号量 Wmutex.设置一个整型变量 Readcount 表示正在读的进程数目,由于只要一个 Reader 进程在读,便不允许 Writer 进程去写。
semaphore rmutex = 1,wmutex = 1; //readcount 和文件的互斥信号量
int readcount=0;
Reader() //这种方式用于——>独木桥问题
{
while(1)
{
P(rmutex); //允许多个进程同时读一个共享对象
if (readcount == 0)
P(wmutex); //缓冲区互斥
readcount ++;
V(rmutex);
perform read operation;
P(rmutex);
readcount --;
if (readcount == 0)
V(wmutex);
V(rmutex);
}
}
Writer()
{
while(1)
{
P(wmutex);
perform write operation;
V(wmutex);
}
}
说个简单问题, 读者 - 写者 问题相当于一个班用自习室抢房间,第一个人来了就开始占房间使用,陆续班上同学进入教室自习,当自习结束且直到最后一个人走了,这个房间不再使用
2. 利用信号量集机制解决 读者 - 写者
增加一个限制,最多只允许 RN 个读者同时都。为此引入一个信号量L,并赋予初值为RN,通过执行 wait(L,1,1) 操作来控制读者数目。每当有一个读者进入时,就要先执行 wait(L,1,1) 操作,使L的值减 1 。当有 RN 个读者进入后,L 便减为 0 ,第 RN+1 个读者要进入读时,必然因为 wait(L,1,1)操作失败而堵塞。
int RN = N;
semaphore L = RN,mx = 1;
void Rreder()
{
while (TRUE)
{
Swait(L,1,1);
Swait(mx,1,0); //1,1,0时没有操作,开关;
perform read operation;
Ssignal(L,1);
}
}
void Writer()
{
while (TRUE)
{
Swait(mx,1,1;L,RN,0);
perform writer operation;
Ssignal(mx,1);
}
}