PV操作经典例题

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、前言🚀🚀🚀

在这里插入图片描述
像极了程序员修BUG

二、正文☀️☀️☀️

1.水果问题

桌上有一个盘子,可以放一个水果;
父亲总是放苹果到盘子中;
母亲总是放香蕉到盘子中。
一个儿子专等吃盘中的香蕉;
而一个女儿专等吃盘中的苹果。
父母只放水果不吃,儿女只吃水果不放。
实现父亲,母亲,儿子,女儿的进程同步。
用PV操作实现下述问题的解

在这个问题中,我们可以定义以下信号量:

empty:表示盘子是否为空,初始值为1(盘子初始为空)
apple:表示是否有一个苹果在盘子中,初始值为0(没有苹果)
banana:表示是否有一个香蕉在盘子中,初始值为0(没有香蕉)	
semaphore empty = 1;  // 盘子为空  
semaphore apple = 0;  // 盘子中没有苹果  
semaphore banana = 0; // 盘子中没有香蕉  
  
// 父亲的进程  
father() {  
    while (true) {  
        P(empty);  // 等待盘子为空  
        // 放置苹果到盘子中  
        V(apple);  // 盘子中有苹果了  
        // 父亲不吃苹果,继续循环  
    }  
}  
  
// 母亲的进程  
mother() {  
    while (true) {  
        P(empty);  // 等待盘子为空  
        // 放置香蕉到盘子中  
        V(banana); // 盘子中有香蕉了  
        // 母亲不吃香蕉,继续循环  
    }  
}  
  
// 儿子的进程  
son() {  
    while (true) {  
        P(banana); // 等待盘子中有香蕉  
        // 从盘子中取出香蕉吃  
        V(empty);  // 盘子空了  
        // 儿子吃完香蕉,继续等待下一个香蕉  
    }  
}  
  
// 女儿的进程  
daughter() {  
    while (true) {  
        P(apple);  // 等待盘子中有苹果  
        // 从盘子中取出苹果吃  
        V(empty);  // 盘子空了  
        // 女儿吃完苹果,继续等待下一个苹果  
    }  
}

在这个实现中,P(empty)操作确保在放置新的水果之前盘子是空的,V(apple)或V(banana)操作表示已经在盘子中放置了苹果或香蕉,并且P(apple)或P(banana)操作确保在取出水果吃之前它确实在盘子中。V(empty)操作是在吃完水果后执行的,表示盘子再次变空

2.和尚打水问题

为了解决这个问题,我们可以使用信号量来控制小和尚和老和尚对水缸的访问,确保每次只有一个和尚在打水或取水,同时保证水缸中的水不会超过其容量,也不会在取水时变为空。

首先,我们定义以下信号量:

empty:表示水缸中的空位数量,初始化为10(因为水缸可以放10桶水)。
full:表示水缸中已装满的水桶数量,初始化为0。
mutex:互斥信号量,用于确保在任何时候只有一个和尚在操作水缸。
初始化() {  
    semaphore empty = 10;  // 水缸初始为空位10个  
    semaphore full = 0;    // 水缸初始为0桶水  
    semaphore mutex = 1;   // 初始时有一个互斥锁可用  
}
小和尚打水() {  
    P(empty); // 等待水缸有空位  
    P(mutex); // 请求互斥锁  
    // 从井里打一桶水放入水缸  
    V(full);  // 水缸中的水桶数量增加  
    V(mutex); // 释放互斥锁  
}  
  
小和尚循环打水() {  
    while (true) {  
        小和尚打水(); // 重复打水直到被停止或其他条件  
        // ... 可能存在的其他逻辑,如休息、等待指示等  
    }  
}
老和尚取水() {  
    P(full);  // 等待水缸中有水  
    P(mutex); // 请求互斥锁  
    // 从水缸中取出一桶水饮用  
    V(empty); // 水缸中的空位数量增加  
    V(mutex); // 释放互斥锁  
}  
  
老和尚循环取水() {  
    while (true) {  
        老和尚取水(); // 重复取水直到被停止或其他条件  
        // ... 可能存在的其他逻辑,如休息、等待指示等  
    }  
}

3.餐厅职员问题

一个快餐厅有4类职员:
(1)领班:接受顾客点菜,出菜单;
(2)厨师:根据菜单,准备顾客的饭菜;
(3)打包工:将做好的饭菜打包;
(4)出纳员:收款并提交食品。
每个职员可被看作一个进程,试用一种同步机制写出能让四类职员正确并发运行的程序

// 定义信号量  
semaphore menuQueue = 0; // 菜单队列,初始为空  
semaphore foodQueue = 0; // 饭菜队列,初始为空  
semaphore mutex = 1;     // 互斥信号量,保护共享资源  
  
// 领班进程  
void headwaiter() {  
    while (true) {  
        // 接收顾客点菜,出菜单  
        Menu menu = receiveOrderFromCustomer();  
          
        // 放置菜单到队列中  
        P(mutex); // 请求互斥锁  
        addToMenuQueue(menu);  
        V(mutex); // 释放互斥锁  
          
        // 通知厨师有新的菜单  
        V(menuQueue);  
    }  
}  
  
// 厨师进程  
void chef() {  
    while (true) {  
        P(menuQueue); // 等待菜单  
          
        // 取出菜单并准备饭菜  
        Menu menu;  
        P(mutex); // 请求互斥锁  
        menu = takeFromMenuQueue();  
        V(mutex); // 释放互斥锁  
          
        Food food = prepareFood(menu);  
          
        // 放置饭菜到队列中  
        P(mutex); // 请求互斥锁  
        addToFoodQueue(food);  
        V(mutex); // 释放互斥锁  
          
        // 通知打包工有新的饭菜  
        V(foodQueue);  
    }  
}  
  
// 打包工进程  
void packer() {  
    while (true) {  
        P(foodQueue); // 等待饭菜  
          
        // 取出饭菜并打包  
        Food food;  
        P(mutex); // 请求互斥锁  
        food = takeFromFoodQueue();  
        V(mutex); // 释放互斥锁  
          
        PackedFood packedFood = packFood(food);  
          
        // 通知出纳员有新的打包食品  
        // 在这个简单的模型中,打包和提交给顾客可能是连续的,因此不需要额外的信号量  
        // 如果打包和收款是分开的,那么可以添加一个信号量来同步  
    }  
}  
  
// 出纳员进程  
void cashier() {  
    while (true) {  
        // 假设打包工已经完成了打包工作,并且直接提交给出纳员  
        // 出纳员收款并提交食品给顾客  
        PackedFood packedFood = getPackedFoodFromPacker(); // 假设有这样一个函数调用  
        collectMoneyFromCustomer(packedFood);  
        deliverFoodToCustomer(packedFood);  
    }  
}  
  
// ... 其他必要的函数定义,如addToMenuQueue, takeFromMenuQueue, prepareFood等 ...  
  
// 主程序或启动函数  
void main() {  
    // 创建并启动进程  
    createProcess(headwaiter);  
    createProcess(chef);  
    createProcess(packer);  
    createProcess(cashier);  
      
    // 等待所有进程结束(在实际系统中,可能需要更复杂的进程管理)  
    // ...  
}

在这个模型中,我们假设receiveOrderFromCustomer, addToMenuQueue, takeFromMenuQueue, prepareFood, addToFoodQueue, takeFromFoodQueue, packFood, collectMoneyFromCustomer, deliverFoodToCustomer等函数都是已经定义好的,并且它们能够正确地处理各自的职责。

menuQueue和foodQueue信号量用于控制菜单和饭菜队列的访问,确保厨师不会在没有菜单时工作,打包工不会在没有饭菜时工作。

mutex互斥信号量用于保护对共享资源的访问(如菜单队列和饭菜队列),确保在任何时候只有一个进程能够修改这些资源。

出纳员进程在这个模型中假设与打包工紧密合作,因此没有引入额外的同步机制。在实际情况中,如果打包和收款是分开的,那么可能需要添加额外的信号量来同步这两个进程。

4.汽车站点问题

在公共汽车上,司机和售票员的活动分别是:
司机的活动:启动车辆,正常行车,到站停车。
售票员的活动:上下乘客,关车门,售票,开车门,上下乘客。
在汽车不停的到站,停站,行驶过程中,这两个活动有什么同步关系?用信号量和P,V操作实现它们的同步。

首先,我们定义以下信号量:

start:表示司机是否可以启动车辆,初始值为0,售票员准备好后才能启动。
door:表示车门的状态,初始值为1,表示车门是关闭的。
当车门需要打开时,其值变为0,表示车门正在打开或已	经打开;当车门关闭时,其值变回1。
司机进程 {  
    while (true) {  
        P(start); // 等待售票员准备好  
        启动车辆();  
        正常行车();  
          
        // 到站停车  
        到站();  
        P(door); // 等待车门关闭  
        停车();  
          
        // 售票员准备好后继续行驶  
    }  
}
售票员进程 {  
    while (true) {  
        // 假设到站时自动触发以下活动  
        上下乘客();  
        关车门();  
        V(door); // 通知司机车门已关闭  
        售票();  
          
        // 假设到达下一站前需要通知司机可以启动  
        V(start); // 通知司机可以启动  
          
        // 准备打开车门迎接下一波乘客  
        开车门();  
        上下乘客();  
        // ...(重复上述过程)  
    }  
}

P(start):司机等待start信号量变为非零值,然后将其减1。如果start为0,则司机阻塞,直到售票员通过V(start)将其加1。
V(start):售票员增加start信号量的值,通知司机可以启动车辆。
P(door):司机等待door信号量变为非零值,然后将其减1。如果door为0,则司机阻塞,直到售票员通过V(door)将其加1。
V(door):售票员在关闭车门后增加door信号量的值,通知司机车门已关闭,可以安全停车或启动。

5.观察者-报告者问题

用P,V操作实现下述问题的解:
观察者和报告者是两个并发执行的进程,观察者不断观察并对通过的卡车计数,报告者定时的将观察者的计数值打印,打印完毕,将计数值清零。

为了使用P(等待)和V(信号)操作实现观察者-报告者问题,我们需要一个共享变量来存储卡车的计数值,以及一个信号量来同步两个进程。我们可以使用一个互斥信号量mutex来确保对计数值的互斥访问,以及一个条件变量reportReady来通知报告者可以打印计数值。
不过,需要注意的是,在标准的P,V操作中并没有直接的条件变量。但我们可以使用信号量和循环等待(忙等待)来模拟条件变量的行为。在这个例子中,我们可以使用两个信号量:mutex用于互斥访问,reportReady用于报告者检查观察者是否准备好了新的计数值。

semaphore mutex = 1; // 互斥信号量,用于保护对计数值的访问  
semaphore reportReady = 0; // 报告者等待信号量,初始化为0,表示还没有新的计数值  
int truckCount = 0; // 共享变量,用于存储卡车计数值  
  
// 观察者进程  
void observer() {  
    while (true) {  
        // 观察到一辆卡车  
        observeTruck();  
          
        // 增加计数值,需要互斥访问  
        P(mutex);  
        truckCount++;  
        V(mutex);  
          
        // 通知报告者有新计数值可以打印  
        V(reportReady);  
    }  
}  
  
// 报告者进程  
void reporter() {  
    while (true) {  
        // 等待观察者通知有新计数值  
        P(reportReady);  
          
        // 打印计数值,需要互斥访问  
        P(mutex);  
        printTruckCount(truckCount);  
        truckCount = 0; // 清零计数值  
        V(mutex);  
          
        // 等待一段时间再次检查  
        sleep(reportingInterval); // 假设有一个函数可以设置等待时间  
    }  
}  
  
// 主程序或启动函数  
void main() {  
    // 创建并启动进程  
    createProcess(observer);  
    createProcess(reporter);  
      
    // ... 其他必要的初始化和等待进程结束的代码 ...  
}  
  
// 假设的函数定义  
void observeTruck() {  
    // 假设的实现,表示观察到一辆卡车  
}  
  
void printTruckCount(int count) {  
    // 假设的实现,用于打印计数值  
}  
  
void sleep(int interval) {  
    // 假设的实现,用于让进程休眠一段时间  
}  
  
// ... 其他必要的函数定义 ...

在这个实现中,reportReady信号量用于同步报告者和观察者。每当观察者观察到一辆卡车并增加计数值后,它会释放reportReady信号量,通知报告者可以打印计数值了。报告者在打印完计数值后会等待一段时间,然后再次检查reportReady信号量,等待观察者提供新的计数值。

6…阅览室问题

假定阅览室最多可同时容纳100个人阅读,读者进入时,必须在阅览室门口的一个登记表上登记,内容包括姓名、座号等,离开时要撤掉登记内容。
用P、V操作描述读者进程的同步算法

为了使用P(等待)和V(信号)操作来描述阅览室问题的读者进程同步算法,我们需要一个信号量来限制同时进入阅览室的读者数量。在这个例子中,我们假设阅览室最多可以容纳100个人,所以我们需要一个信号量roomSize,其初始值设置为100。

此外,为了模拟登记和撤掉登记内容的操作,我们还需要一个互斥信号量mutex来确保登记表的互斥访问。

semaphore roomSize = 100; // 控制进入阅览室的读者数量  
semaphore mutex = 1; // 控制对登记表的互斥访问  
  
// 读者进程  
void readerProcess(string name, int seatNumber) {  
    while (true) { // 假设读者可以多次进出阅览室  
        // 请求进入阅览室  
        P(roomSize); // 等待阅览室有空位  
          
        // 登记进入阅览室  
        P(mutex); // 请求对登记表的互斥访问  
        registerEntry(name, seatNumber); // 登记姓名和座号  
        V(mutex); // 释放对登记表的互斥访问  
          
        // 在阅览室阅读...  
        readInLibrary();  
          
        // 准备离开阅览室  
        P(mutex); // 请求对登记表的互斥访问  
        removeEntry(name, seatNumber); // 撤掉登记内容  
        V(mutex); // 释放对登记表的互斥访问  
          
        // 离开阅览室,释放一个位置  
        V(roomSize); // 通知其他读者阅览室有空位了  
          
        // 读者可以选择继续阅读或结束进程  
        // ...  
    }  
}  
  
// 假设的函数定义  
void registerEntry(string name, int seatNumber) {  
    // 实现登记的逻辑,比如将姓名和座号写入文件或数据库  
}  
  
void removeEntry(string name, int seatNumber) {  
    // 实现撤掉登记的逻辑,比如从文件或数据库中删除姓名和座号  
}  
  
void readInLibrary() {  
    // 读者在阅览室阅读的逻辑  
}  
  
// 主程序或启动函数  
void main() {  
    // 创建并启动多个读者进程  
    // ...  
      
    // 等待所有读者进程结束或执行其他管理操作  
    // ...  
}

三、总结🍓🍓🍓

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值