提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、前言🚀🚀🚀
像极了程序员修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() {
// 创建并启动多个读者进程
// ...
// 等待所有读者进程结束或执行其他管理操作
// ...
}