操作系统-PV操作-生产者消费者-读者写者问题

PV操作:

PV操作包括P操作(Proberen,尝试)和V操作(Verhogen,增加),它们通过对信号量的操作来协调进程之间的行为。

信号量(Semaphore):(理解为flag这样的变量吧)值表示可用资源的数量或等待资源的进程数量。
P操作(Proberen):用于申请资源。当进程执行P操作时,会检查信号量的值:如果信号量的值大于0,则将其减1,进程继续执行。如果信号量的值为0,则进程被阻塞,等待信号量变为正值。
V操作(Verhogen):V操作用于释放资源。当进程执行V操作时,会将信号量的值加1;如果信号量的值大于0,则进程继续执行。如果信号量的值小于或等于0,则唤醒一个等待该信号量的进程。

特点: 
不可中断:PV操作不可被中断;
成对出现:上有P,下必有V。(资源只是被借用,借完要还。)

(经典的)司机与售票员问题:

这里PV操作用于同步

(经典的)生产者-消费者问题:

比如有一个槽,生产者不断往里放东西,消费者不断往外拿东西;如果槽满了,生产者就不能再生产东西往槽里放了;再如果槽空了,消费者就啥也不拿了;此外,生产者和消费者不同时访问这个槽。

这个槽叫做:缓冲区。现在我们使用PV操作来描述这个问题,然后你就会发现这PV操作是多么好用……

我们先使用几个信号量:empty表示缓冲区空闲的位置,full表示缓冲区被占用的位置,mutex表示进程对缓冲区进行了互斥的访问(mutex出现,代表着“我在访问临界区,别人不要进来”)。

其实不需要纠结Pmutex和Vmutex到底是啥操作,你就理解为上厕所时要给门上锁开锁。

下面时生产者和消费者的代码,千万要注意,信号量empty和full是空闲和占用(there exists)的意思啊!不是全空或全满!(not all)

void producer() {
    while (true) {
        // 生产数据
        produce_item();

        // 等待空闲位置
        P(empty);

        // 进入临界区,互斥访问缓冲区
        P(mutex);
        // 将数据放入缓冲区
        put_item_into_buffer();
        V(mutex);

        // 增加已占用位置的数量
        V(full);
    }
}
void consumer() {
    while (true) {
        // 等待有数据可消费
        P(full);

        // 进入临界区,互斥访问缓冲区
        P(mutex);
        // 从缓冲区取出数据
        take_item_from_buffer();
        V(mutex);

        // 增加空闲位置的数量
        V(empty);

        // 消费数据
        consume_item();
    }
}

那么再讲一下这个吧:

某寺庙有小和尚、老和尚若干,庙内有一个水缸,由小和尚提水入缸,供老和尚饮用。
水缸可以容纳30桶水,每次入水、取水仅为1桶,不可同时进行。水取自同一口井中,水井径
窄,每次只能容纳一个水桶取水。设水桶个数为5个,试用信号量和PV操作给出老和尚和小
和尚的活动。

典型的生产者消费者问题(对吧):

生产者:小和尚
消费者:老和尚
缓冲区(有限的且被主要针对的):水缸,长度(容量)为30桶水。
有限的(除了缓冲区):桶(5个)。
临界区(冲突,不能同时进行的):取水/入水(mutex的地方,每次入水、取水仅为1桶,不可同时进行)

悟:PV操作针对所有的有限资源。我们这里有两个缓冲区 -- 水缸,桶,那么就分别会有一对PV操作来描述他们。

void little_monk() {
    while (true) {
        // 等待空闲位置
        P(empty);

        // 等待可用的水桶
        P(bucket);

        // 进入临界区,互斥访问水缸
        P(mutex);
        // 将水倒入水缸
        pour_water_into_tank();
        V(mutex);

        // 增加已占用位置的数量
        V(full);

        // 增加可用的水桶数量
        V(bucket);
    }
}
void old_monk() {
    while (true) {
        // 等待有水可饮用
        P(full);

        // 等待可用的水桶
        P(bucket);

        // 进入临界区,互斥访问水缸
        P(mutex);
        // 从水缸中取水
        take_water_from_tank();
        V(mutex);

        // 增加空闲位置的数量
        V(empty);

        // 增加可用的水桶数量
        V(bucket);
    }
}

接着讲第二个经典操作:读者写者问题。

纯沙比的问题:描述为:比如你有一份共享文档,可以很多人一起读,但是同一时刻只有一个人才可写。当然我们说的不是踏马的文档。我是说进程。理解这个问题之后,看看官方是怎么说的:

“读者-写者问题是操作系统中经典的同步问题,用于描述多个进程(线程)对共享资源的访问问题。在这个问题中,共享资源可以被多个读者进程并发访问但写者进程在写入数据时需要独占访问。读者和写者之间需要通过同步机制来协调它们的行为,以避免数据不一致或冲突。”

同上面的读者写者问题一样,我们先看:

临界区:这份文档
冲突的:读者和写者,写者和写者

引入以下信号量和变量:
mutex:用于对共享资源的互斥访问,初始值为1。
wrt:用于控制写者对共享资源的独占访问,初始值为1。
rc:一个整数变量,用于记录当前正在读取的读者数量,初始值为0。

然而这里的mutex不是描述这份“文档”(虽然看起来文档才是临界区):

在读者-写者问题中,mutex通常用于:
保护共享资源的访问,例如读者计数器rc的更新。
确保写者在写入时对共享资源的独占访问。

void writer() {
    while (true) {
        // 等待独占访问
        P(wrt);

        // 修改共享资源
        write_shared_resource();

        // 释放独占访问
        V(wrt);
    }
}
void reader() {
    while (true) {
        // 等待可以读取
        P(can_read);

        // 等待进入读取状态
        P(mutex);
        rc++;
        if (rc == 1) {
            // 如果是第一个读者,阻塞写者
            P(wrt);
        }
        V(mutex);

        // 读取共享资源
        read_shared_resource();

        // 等待离开读取状态
        P(mutex);
        rc--;
        if (rc == 0) {
            // 如果是最后一个读者,释放写者
            V(wrt);
        }
        V(mutex);

        // 释放可以读取
        V(can_read);
    }
}

这里出现了两组PVmutex:第一组:置Pwrt的时候,只需要一个读者,第二组:释放wrt的时候,也只需要一个读者,避免了多个读者同时阻止/释放wrt的情况。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值