PV操作-同步与互斥

浅记学习PV操作的部分题目。

消费者与生产者

单生产者与单消费者


理解PV操作可以从消费者与生产者之间的关系入手。

一个生产者与消费者的情况


消费者想要获取一份商品,需要检查市场中该商品是否有余量:

  • 如果剩余商品足够,则获取该商品。
  • 如果剩余商品不足,则等待生产者生产该商品,生产后再获取。

消费者获取到商品之后应通知生产者:市场刚刚被消费了一份,出现了空位,你要继续生产。

生产者想要生产一份商品,需要检查市场中是否有空间允许加入新的商品:

  • 如果市场没有饱和,存在相关需求,则生产一份商品加入市场。
  • 如果市场已经饱和,没有位置插入商品,则等待市场出现空缺,有位置后再生产。

生产者生产出商品之后应通知消费者:刚刚制造了商品,市场出现了余量,你可以来买了。

尝试用代码表示

empty表示生产者当前有多少缓冲区可用。

  • 1:有一个缓冲区可用。
  • 0:没有缓冲区可用了。
  • -1:没有缓冲区可用,同时有一个生产者在排队等待生产。

full表示消费者有多少商品可以使用。

  • 1:有一个商品可用。
  • 0:没有商品可用。
  • -1:没有商品可用,同时有一个消费者在排队等待上架货物。
//生产者进程
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);//唤醒生产者
}

PV操作底层

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操作,要看实际的需求:

  • P可以理解为:申请、占用,本质是-1。
  • V可以理解为:释放、唤醒,本质是+1。
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); // 离开座位
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WuShF.top

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值