提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 一、前言🚀🚀🚀
- 二、正文☀️☀️☀️
- 1.水果问题
- 2.和尚打水问题
- 3.餐厅职员问题
- 4.汽车站点问题
- 5.观察者-报告者问题
- 6..阅览室问题
- 三、总结🍓🍓🍓
一、前言🚀🚀🚀
像极了程序员修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() { // 创建并启动多个读者进程 // ... // 等待所有读者进程结束或执行其他管理操作 // ...
}