浅记学习PV操作的部分题目。
理解PV操作可以从消费者与生产者之间的关系入手。
一个生产者与消费者的情况
消费者想要获取一份商品,需要检查市场中该商品是否有余量:
消费者获取到商品之后应通知生产者:市场刚刚被消费了一份,出现了空位,你要继续生产。
生产者想要生产一份商品,需要检查市场中是否有空间允许加入新的商品:
生产者生产出商品之后应通知消费者:刚刚制造了商品,市场出现了余量,你可以来买了。
尝试用代码表示
empty表示生产者当前有多少缓冲区可用。
full表示消费者有多少商品可以使用。
//生产者进程
while(1){
//P(empty)
empty--;//尝试生产一份商品
if(empty<0)//没地方了,堵塞等待
sleep(s1.queue);//有地方再生产
full++;//生产完了,增加一份
//V(full)
if(full<=0){//有消费者还在等待
wake_up(s2.queue);//唤醒消费者
}
}
//消费者进程
while(1){
//P(full)
full--;//尝试消耗一份商品
if(full<0)//商品不够了,堵塞等待
sleep(s2.queue);//等着来货
//V(empty)
empty++;//有货,消耗一份
if(empty<=0)//有生产者在堵塞
wake_up(s1.queue);//唤醒生产者
}
struct semaphore{
//记录资源的个数
int value;
//记录等待在该信号上的进程
PCB* queue;
}
P(semaphore S){
s.value--;
if(s.value<0)
sleep(s.queue);
}
V(semaphore S){
s.value++;
if(s.value<=0)
wake_up(s.queue);
}
P操作就是信号量-1,如果有余量,允许操作,则继续操作,否则进入等待状态。
V操作就是信号量+1,如果有余量,允许操作,则唤醒一个等待中的进程。
代码表示
如果市场只允许在同一时间执行加入或取出一个操作,那么需要在向市场中添加、取出货物时进行PV操作。
并且初始值为1,表示只有这一个资源,当被一个进程占用时,其他进程将无法访问。
producter(){
while(True){
produce an item;
P(empty);
P(mutex);
add item to buffer;
V(mutex);
V(full);
}
}
consumer(){
while(True){
P(full);
P(mutex);
remove an item from buffer;
V(mutex);
V(empty);
consume the item;
}
}
dad(){
while(True){
prepare an apple;//准备一个苹果
P(plate);//看看盘子有没有位置
put an apple;//有的话把苹果放进去
V(apple);//苹果就绪了
}
}
daughter(){
while(True){
P(apple);//看看此时有没有苹果
take an apple;//有的话取出苹果
V(plate);//腾出盘子
eat the apple;//吃掉苹果
}
}
mom(){
while(True){
prepare an orange;//准备一个橙子
P(plate);//看看盘子有没有位置
put an orange;//把橙子放进去
V(orange);//橙子就绪了
}
}
son(){
while(True){
P(orange);//看看此时有没有橙子
take an orange;//有的话取出橙子
V(plate);//腾出盘子
eat the orange;//吃掉橙子
}
}
有以下需求:
下面的伪代码存在问题:如果一直有读者请求访问,那么写者可能永远无法获取到资源。
//定义信号量
int count=0;
semaphore mutex=1;
semaphore rw=1;
//读者进程
reader(){
while (True)
{
P(mutex);//请求操作临界资源
if(count==0)//我是第一个来的
P(rw);//占用资源
count++;//访问人数++
V(mutex);//释放临界资源
reading...
P(mutex);//请求操作临界资源
count--;//我要走了,访问人数--
if(count==0)//我是最后一个读者
V(rw);//释放资源
V(mutex);//释放临界资源
}
}
writer(){
while (True)
{
P(rw);//请求写操作
writing...
V(rw);//释放临界资源
}
}
通过一个新的信号量w
:
// 定义信号量
int count = 0;
semaphore mutex = 1;
semaphore rw = 1;
semaphore w = 1;
// 读者进程
reader()
{
while (True)
{
P(w);
P(mutex); // 请求操作临界资源
if (count == 0) // 我是第一个来的
P(rw); // 占用资源
count++; // 访问人数++
V(mutex); // 释放临界资源
V(w);
reading... P(mutex); // 请求操作临界资源
count--; // 我要走了,访问人数--
if (count == 0) // 我是最后一个读者
V(rw); // 释放资源
V(mutex); // 释放临界资源
}
}
writer()
{
while (True)
{
P(w);
P(rw); // 请求写操作
writing... V(rw); // 释放临界资源
V(w);
}
}
互斥隐藏在同步中
semaphore offer1=0,offer2=0,offer3=0,finish=0;
int num=0;
Smoker1(){
while(True){
P(offer1);
抽烟...
V(finish);
}
}
Smoker2(){
while(True){
P(offer2);
抽烟...
V(finish);
}
}
Smoker3(){
while(True){
P(offer3);
抽烟...
V(finish);
}
}
Supplier(){
while(True){
//放材料
P(finish);
num++;
num=num%3;
if(num==0)
V(offer1);
else if(num==1)
V(offer2);
else
V(offer3);
}
}
需要信号量mutex
,在申请chopstick
之前申请信号量mutex
,拿取左右筷子视为一次原子操作。
以避免“所有哲学家同时拿到了左筷子,等待右筷子,导致都没有两双筷子”的情况发生。
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex=1;
Philosopher(){
do{
P(mutex);
P(chopstick[i]);
P(chopstick[(i+1)%5]);
V(mutex);
eat...
V(chopstick[i]);
V(chopstick[(i+1)%5]);
think...
}while(True);
}
与前面几道不同,这里营业员和顾客方法内,各自P和V的数量是不相等的。
到底是P是V,用不用PV操作,要看实际的需求:
semaphore seats = 10; // 有十个座位的资源信号量
semaphore mutex = 1; // 取号机互斥的信号量
semaphore haveCustem = 0; // 顾客与营业员同步,无顾客时营业员休息
process 营业员
{
while (True)
{
P(haveCustem); // 有没有顾客需要服务
叫号...
为顾客服务...
}
}
process 顾客
{
P(seats); // 是否有座位
P(mutex); // 申请使用取号机
从取号机上取号...
V(mutex); // 取号完毕
V(haveCustem); // 通知营业员有新顾客到来
等待营业员叫号... V(seats); // 离开座位
}