Operating System-进程/线程内部通信-信号量、PV操作的实现和应用(解决哲学家进餐和生产者消费者问题),system-pv


本文主要内容:

  • 信号量的实现
  • 利用信号量解决哲学家用餐问题
  • 利用信号量解决生产者消费者问题

一、信号量的实现

1.1 信号量结构

typedef struct
{
    int value;
    struct process * list
} semaphore;

value代表当前信号量可以使用的数量,list代表当前信号量上所等待的进程。

1.2 P操作实现

P(semaphore * s)
{
    s.value--;
    if(s.value < 0)
    {
        add current process to s.list;
        block();
    }
}

如果s.value<0。说明没有资源可用,当前进程加入list(等待进程列表),并且block当前进程。

1.3 V操作实现

V(semaphore * s)
{
    s.value++;
    if(s.value <= 0)
    {
        remove a process A from s.list;
        wakeup(A);
    }
}

 s++代表释放了一个资源,如果s.value++后还是<=0。说明已经有至少一个进程在等待资源,这个时候就从list中拿出几个进程,wakeup这个进程,这样这个进程就可以开始使用这个刚刚被释放的资源了(这个进程不一定马上开始运行,至少是ready状态,什么时候运行取决于CPU的调度算法)。

 1.4 忙等待问题

之前的Peterson的方案是通过忙等待来达到进程间的互斥访问的。PV操作其实也是无法避免的,PV操作的前提是不会有两个进程针对一个信号量在同时进行PV操作。这个时候信号量是信号量就是临界区域,如何让P和V操作对于信号量的操作互斥?

操作系统针对这个也是通过忙等待来完成的(一个进程对信号量进行操作的时候,其他进程一直等待)。不过把忙等待最终防止在信号量层面是最大限度的减少了忙等待的时间,因为pv操作的时间是非常快的。

二、利用信号量解决哲学家用餐问题

2.1 问题演示

五个哲学家,如上图所示:

  • 每两个哲学家之间都有一只筷子,总共5只
  • 哲学家都在思考,饿的时候就会拿起两边的筷子去吃饭
  • 只有拿了两支筷子后才能开始吃饭

很明显,如果一个哲学家在想吃饭的时候如果其任何一个邻居在吃饭,他是没有办法吃饭的,筷子不够(要么只有一只筷子或者一只都没有)。

2.2 解决方案

void philosopher(i)
{
    while(true)
    {
        P(chopstick[i]);
        P(chopstick[( i + 1 ) % 5]);
        eat();
        V(chopstick[i]);
        V(chopstick[( i + 1 ) % 5]);
        think();
    }
}

i代表第i个哲学家(0,1,2,3,4,5)。程序的核心思想就是利用PV操作让哲学家一直等待直到其左右两边的筷子都空闲再开始吃饭,吃完以后再把筷子放回去。

哲学家和筷子的编号

当哲学家0想用餐时,等待0,1筷子,哲学家2想用餐时等待2,3筷子。。

2.3 问题

上面的方案有个缺点:

当所有哲学家同时想用餐时,就会发生死锁,永远等待( starvation):

该问题下一篇博文专门介绍如何解决starvation。

三、利用信号量解决生产者消费者问题

 之前的文章引入了生产者和消费者模式,但是该模式有有问题,wakeup有可能会丢失,其实核心问题还是因为对count(buffer容量)的访问没有做进程互斥访问,对buffer的访问没有做互斥访问,通过PV操作可以解决该问题

设置三个信号量:

  • full,代表buffer中item的数量,默认为0
  • empty代表buffer中空闲的数量,默认为buffer的容量n
  • mutex,提供对于buffer的互斥操作,1代表可以访问buffer。0代表不可以访问buffer,初始值为1;
void producer()
{
    while(true)
    {
        produce_item();
        P(empty);//空闲容量-1,没有空闲容量,则等待,且进入block列表
        P(mutex);//检测buffer是否可以访问
        insert_item();
        V(mutex);//释放buffer的访问权
        V(full);//item数量+1,如果有consumer等待,则唤醒一个consumer
    }
}

void consumer()
{
    while(true)
    {
        P(full);//item数量-1,如果没有item可以消费,则进入block列表
        p(mutex);//检测buffer是否可以访问
        remove_item();
        V(mutex);//释放buffer访问权限
        V(empty);//空闲容量+1,如果有producer等待吗,则唤醒一个producer
        consume_item();
    }
}

 

相关内容

    暂无相关文章