进程同步之理发师问题
2016-08-07 11:10
295 查看
进程同步之理发师问题
@(操作系统)[进程同步]description
假设有一个理发店只有一个理发师,一张理发时坐的椅子,若干张普通椅子顾客供等候时坐。没有顾客时,理发师就坐在理发的椅子上睡觉。顾客一到,他不是叫醒理发师,就是离开。如果理发师没有睡觉,而在为别人理发,他就会坐下来等候。如果所有的椅子都坐满了人,最后来的顾客就会离开。在出现竞争的情况下问题就来了,这和其它的排队问题是一样的。实际上,与哲学家就餐问题是一样的。如果没有适当的解决方案,就会导致进程之间的“饿肚子”和“死锁”。
如理发师在等一位顾客,顾客在等理发师,进而造成死锁。另外,有的顾客可能也不愿按顺序等候,会让一些在等待的顾客永远都不能理发。
解决方案
最常见的解决方案就是使用三个信号量(Semaphore):一个给顾客信号量,一个理发师信号量(看他自己是不是闲着),第三个是互斥信号量(Mutual exclusion,缩写成mutex)。一位顾客来了,他想拿到互斥信号量,他就等着直到拿到为止。顾客拿到互斥信号量后,会去查看是否有空着的椅子(可能是等候的椅子,也可能是理发时坐的那张椅子)。如果没有一张是空着的,他就走了。如果他找到了一张椅子,就会让空椅子的数量减少一张,这位顾客接下来就使用自己的信号量叫醒理发师。这样,互斥信号标就释放出来供其他顾客或理发师使用。如果理发师在忙,这位顾客就会等。理发师就会进入了一个永久的等候循环,等着被在等候的顾客唤醒。一旦他醒过来,他会给所有在等候的顾客发信号,让他们依次理发。
PV操作
顾客信号量 = 0 理发师信号量 = 0 互斥信号量mutex = 1 // 椅子是理发师和顾客精进程都可以访问的临界区 int 空椅子数量 = N //所有的椅子数量 理发师(线程/进程) While(true){ //持续不断地循环 P(顾客) //试图为一位顾客服务,如果没有他就睡觉(进程阻塞) P(互斥信号量) //如果有顾客,这时他被叫醒(理发师进程被唤醒),要修改空椅子的数量 空椅子数量++ //一张椅子空了出来 V(理发师) //现在有一个醒着的理发师,理发师准备理发,多个顾客可以竞争理发师互斥量,但是只有一个顾客进程可以被唤醒并得到服务 V(互斥信号量) //释放椅子互斥量,使得进店的顾客可以访问椅子的数量以决定是否进店等待 /* 理发师在理发 */ } 顾客(线程/进程) while(true) { //持续不断地循环 P(互斥信号量) //想坐到一张椅子上 if (空椅子数量 > 0) { //如果还有空着的椅子的话 空椅子数量-- //顾客坐到一张椅子上了 V(顾客) //通知理发师,有一位顾客来了 V(互斥信号量) //顾客已经坐在椅子上等待了,访问椅子结束,释放互斥量 P(理发师) //该这位顾客理发了,如果理发师还在忙,那么他就等着(顾客进程阻塞) /* 竞争到了理发师则该顾客开始理发 */ } else { //没有空着的椅子 V(互斥信号标) //不要忘记释放被锁定的椅子 /* 顾客没有理发就走了 */ } }
c++代码
#include <thread> #include <mutex> #include <iostream> #include <vector> #include <cmath> #include <condition_variable> using namespace std; int chairs = 10; int empty_chairs = chairs; // 空椅子数 int customers = 0; int barbers = 1; condition_variable cv_barbers; // 当有顾客到时需通知理发师 mutex chairs_mtx, cus_mtx, bar_mtx; /** * 理发师进程,阻塞理发师的请况 * 1. 没有顾客,则睡觉(阻塞) * 2. 访问临界区受阻,此时临界区正在被顾客访问 * @param i [description] */ void barber(int i) { while (true) { unique_lock<mutex> lck(bar_mtx); cv_barbers.wait(lck, [] { if (customers > 0) { cout << "有顾客,理发师被唤醒" << endl; return true; } else { cout << "没有顾客,理发师睡觉" << endl; return false; } }); unique_lock<mutex> lck2(chairs_mtx); customers--; empty_chairs++; /* cut hair*/ cout << "理发师给顾客理发" << endl; lck2.unlock(); // 理发时不断有顾客进来 this_thread::sleep_for(std::chrono::microseconds(10)); } } /** * 顾客进程,阻塞顾客进程的情况有两种 * 1. 访问临界区(检查是否有空闲的椅子)时发现理发师进程也在访问临界区,P(chairs_mtx) * 没有多余的椅子并不是阻塞顾客进程,直至有空闲椅子,而是直接离开,即该顾客不排队理发 * @param i [description] */ void customer(int i) { unique_lock<mutex> lck(chairs_mtx); if (empty_chairs > 0) { empty_chairs--; customers++; cv_barbers.notify_one(); cout << "顾客 " << i << " 等待理发" << endl; //this_thread::sleep_for(std::chrono::milliseconds(100)); lck.unlock(); } else { /* 进程退出,不再理发了 */ cout << "顾客 " << i << " 没有位置,离开" << endl; lck.unlock(); /* leave */ } } int main() { thread t1 = thread(barber, 1); vector<thread> v; for (size_t i = 0; i < 20; i++) { v.push_back(thread(customer, i + 1)); } t1.join(); for (size_t i = 0; i < v.size(); i++) { v[i].join(); } system("pause"); return 0; }
以下是程序运行一种可能的结果:
1. 理发师线程首先被调度,此时没有任何顾客,理发师线程阻塞。
2. 然后顾客线程1,9,3一次被调度。即在理发师睡觉期间进来了三个顾客。在第一个顾客进店时已经通过notify_one唤醒了理发师线程(就绪态)。在理发师醒来的同时,顾客9和3也进店了。
3. 理发师线程再次被调度,此时理发师线程变为执行态,理发师给顾客理发
4. 理发师理发期间,顾客6又进店了
5. 理发师已经服务完一名顾客再次调度,此时店内已经有顾客9,3,4,6。理发师继续选择下一个顾客进行服务。
6. 在理发师还在服务的时候,顾客7,8,2,10,11,12依次进店坐在椅子上等待理发。
7. 而顾客13,14,15,16,17,18,19,20,5进店之后发现没有多余的椅子了,顾客离开,不会等待直至有空闲椅子,因此这些顾客线程退出。此时只剩下9,3,4,6,7,8,2,10,11,12等顾客在店中,他们的线程也退出了,因为它们都在椅子上坐着,随着理发师线程的不断调度,它们总是会被服务的。
8. 此后没有任何顾客线程了,只有不断的理发师线程再调度,依次为各个顾客理发,直至没有任何顾客在椅子上等待。最后理发师服务完所有顾客又发现没有顾客了,理发师开始睡觉,线程阻塞。
输出结果如下:
没有顾客,理发师睡觉 顾客 1 等待理发 顾客 9 等待理发 顾客 3 等待理发 有顾客,理发师被唤醒 顾客 4 等待理发 理发师给顾客理发 顾客 6 等待理发 有顾客,理发师被唤醒 顾客 7 等待理发 顾客 8 等待理发 顾客 2 等待理发 顾客 10 等待理发 顾客 11 等待理发 顾客 12 等待理发 顾客 13 没有位置,离开 顾客 14 没有位置,离开 顾客 15 没有位置,离开 顾客 16 没有位置,离开 顾客 17 没有位置,离开 顾客 18 没有位置,离开 顾客 19 没有位置,离开 顾客 20 没有位置,离开 顾客 5 没有位置,离开 理发师给顾客理发 有顾客,理发师被唤醒 理发师给顾客理发 有顾客,理发师被唤醒 理发师给顾客理发 有顾客,理发师被唤醒 理发师给顾客理发 有顾客,理发师被唤醒 理发师给顾客理发 有顾客,理发师被唤醒 理发师给顾客理发 有顾客,理发师被唤醒 理发师给顾客理发 有顾客,理发师被唤醒 理发师给顾客理发 有顾客,理发师被唤醒 理发师给顾客理发 有顾客,理发师被唤醒 理发师给顾客理发 没有顾客,理发师睡觉
相关文章推荐
- 应用领航:盘点那些年我们一起追过的OS
- 无奇不有!盘点各国自己开发的操作系统
- 可自定义oem的萝卜家园 Ghost XP 新春装机版 V200801 下载
- C#实现判断操作系统是否为Win8以上版本
- js获取本机操作系统类型的两种方法
- Linux操作系统添加新硬盘方法
- Javascript中获取浏览器类型和操作系统版本等客户端信息常用代码
- java如何获取本地操作系统进程列表
- Linux rdesktop操作系统下远程登录Windows XP桌面
- 32位操作系统认出超出4G内存的方法
- Linux rpm tar 操作系统下软件的安装与卸载方法
- JavaScript 获取用户客户端操作系统版本
- jsp 获取客户端的浏览器和操作系统信息
- Windows 操作系统的安全设置
- php判断当前操作系统类型
- PHP获取用户的浏览器与操作系统信息的代码
- Perl操作系统环境变量的脚本代码
- javascript获取本机操作系统类型的方法
- 封装好的js判断操作系统与浏览器代码分享
- Android操作系统介绍之11种传感器