多线程同步——哲学家吃饭问题
最近在进行多线程编程的学习,学习过程中想起了复习操作系统的时候见过的“哲学家吃饭问题”。当时理解并不算透彻。于是就趁这个机会,利用最近所学模拟一下这个过程吧。
一、问题描述
二、问题分析
哲学家就餐问题需要协调考虑两个问题:
1、不能让哲学家饿死(避免死锁);
2、要尽量提升吃饭的效率,也就是同一时间尽量让多一些哲学家吃饭(最多同时两个)。
我们考虑这两个问题,然后设计我们的策略。
我们可以考虑用五个互斥量来表示五把叉子,用五个整型来表示“盘子里剩下的面条还有多少”。
1、可能会导致死锁的一种最糟糕策略
某个哲学家的吃面步骤如下:
//while(true)
//{
// 思考;
// 给左边叉子上锁;//去拿左边叉子
// 给右边叉子上锁;//去拿右边叉子
// 吃面条;
// 给左边叉子解锁;//去拿左边叉子
// 给右边叉子解锁;//去拿右边叉子
//}
对于上面的代码,让我们考虑一种情况:如果某一时刻,所有哲学家都拿了自己左边的叉子,接下来他们都要去拿右边的叉子。此时,他们都会发现右边已经没有叉子了。但他们只有在吃完面条后才会放下自己左手的叉子,于是他们每个人左手拿着叉子面面相觑,都不愿意让出自己的叉子,陷入了死锁。因此,解决哲学家吃饭问题的关键点在于,如何避免死锁的发生。
如果每次只允许一个哲学家吃饭,那就可以确保不产生死锁。但理论上可以允许两个不相邻的哲学家同时吃面条,因此这种策略造成了叉子资源的浪费。
2、解决哲学家吃饭问题的一种思路
要解决哲学家就餐的问题,有一个基本的指导思想:放下叉子的时候,可以先放左边再放右边。但拿叉子的时候,要么不拿(缺了左边或右边的叉子),要么一次拿两把叉子!
问题来了,策略看起来很简单,但程序实现的关键点在于,如何模拟“同时拿起左右两把叉子,但凡少一个就不拿”?
(1)用五个互斥量模拟五把叉子
利用C++11中mutex类的try_lock成员函数。每个哲学家想要吃面条时,首先尝试拿起左手的叉子。如果能拿到,再尝试拿起右手的叉子。如果能拿到两把叉子,就吃面,吃完后释放两把叉子。如果无法拿到右边的叉子,就释放左边的叉子。假设一盘面条的数量为50,每个哲学家每次吃掉10到20的面条(随机),代码如下:
#include<iostream>
#include<thread>
#include<mutex>
#include<ctime>using namespace std;
using namespace std::this_thread;
using namespace std::chrono;//用五个互斥量表示五支叉子
mutex fork[5];//用一个规模为5的整型数组来表示每个盘子剩余的面条
int nuddles[5] = { 50,50,50,50,50 };//吃面条函数
void eat(int id) {//id为哲学家编号while (true) {//尝试同时拿起左手边和右手边的叉子if (fork[id].try_lock()) {//先尝试拿起左手边的叉子//如果能拿起左手边的叉子,再拿右手边的if (fork[(id + 1) % 5].try_lock()) {//如果也能拿起右手边的叉子,吃面条//开吃,每次吃掉的面条数量为10-20之间的一个数字printf("哲学家%d开始吃面条!\n", id + 1);srand((unsigned)time(NULL));int numbereated = rand() % (20 - 10 + 1) + 10;sleep_for(seconds(numbereated % 10));//吃一段时间nuddles[id] -= numbereated;if (nuddles[id] <= 0) {printf(" 哲学家%d吃完了他的所有面条!\n", id+1);fork[id].unlock();fork[(id + 1) % 5].unlock();break;}printf("哲学家%d吃完,面条剩余%d!\n", id+1, nuddles[id]);fork[id].unlock();fork[(id + 1) % 5].unlock();sleep_for(milliseconds(200));//吃完之后歇一会,让其它哲学家能够吃上}else {//如果没办法拿起右手边的叉子,记得要把左手边的叉子放下来fork[id].unlock();sleep_for(milliseconds(100));//歇一会(思考),让其它哲学家能够吃上}}}
}int main()
{printf("开始吃饭!每人拥有50面条\n");thread t1(eat, 0);thread t2(eat, 1);thread t3(eat, 2);thread t4(eat, 3);thread t5(eat, 4);t1.join(); t2.join(); t3.join(); t4.join(); t5.join();printf("所有哲学家吃完!\n");return 0;
}
程序运行结果如下:
上面的代码乍看起来是没问题的,但存在隐患。我们考虑一下,如果某一时刻,所有哲学家都拿了自己左边的叉子,接下来他们都要去拿右边的叉子。当这种情况发生时,他们都会发现右边已经没有叉子了。于是他们都把自己左边的叉子放回桌子。然后他们又发现左边有叉子,再拿起来,然后右边还是没有叉子,于是又放下,循环往复,还是陷入了死锁。这种情况虽然比较难遇到,但也不是不可能的。毕竟上面代码并没有遵循“当且仅当自己左边和右边的叉子都在的时候,才同时拿起两把叉子”这个原则。
(2)用临界区来实现“同时拿叉子”
要实现“同时拿两把叉子”这个操作,可以把“拿叉子”动作设为临界区。
#include<process.h>
#include<Windows.h>
#include<iostream>
#include<ctime>using namespace std;//用五个bool型变量表示五把叉子
//true表示可以拿,false表示已经被拿走
bool fork[5] = { true,true,true,true,true };//用一个规模为5的整型数组来表示每个盘子剩余的面条
int nuddles[5] = { 50,50,50,50,50 };//定义临界区结构为全局变量
CRITICAL_SECTION Section;//拿叉子
bool takeForks(int id) {//进入临界区,确保每个时刻只有一个哲学家在拿叉子EnterCriticalSection(&Section);bool leftfork = fork[id], rightfork = fork[(id + 1) % 5];if (leftfork && rightfork) {//同时拿起fork[id] = false;fork[(id + 1) % 5] = false;}LeaveCriticalSection(&Section);return leftfork && rightfork;
}//吃面条函数
void eat(void *param) {int id = *(int*)param;while (true) {if (takeForks(id)) {printf("哲学家%d开始吃面条!\n", id + 1);srand((unsigned)time(NULL));int numbereated = rand() % (20 - 10 + 1) + 10;//Sleep((numbereated%10)*1000);nuddles[id] -= numbereated;if (nuddles[id] <= 0) {printf(" 哲学家%d吃完了他的所有面条!\n", id + 1);//EnterCriticalSection(&Section);fork[id] = true;fork[(id + 1) % 5] = true;//LeaveCriticalSection(&Section);break;}printf("哲学家%d吃完,面条剩余%d!\n", id + 1, nuddles[id]);//EnterCriticalSection(&Section);fork[id] = true;fork[(id + 1) % 5] = true;//LeaveCriticalSection(&Section);Sleep(200);//思考}}
}int main()
{//初始化临界区InitializeCriticalSection(&Section);printf("开始吃饭!每人拥有50面条\n");int p[5] = { 0,1,2,3,4 };uintptr_t t1 = _beginthread(eat, 0, (void*)p);uintptr_t t2 = _beginthread(eat, 0, (void*)(p + 1));uintptr_t t3 = _beginthread(eat, 0, (void*)(p + 2));uintptr_t t4 = _beginthread(eat, 0, (void*)(p + 3));uintptr_t t5 = _beginthread(eat, 0, (void*)(p + 4));HANDLE hArr[] = { (HANDLE)t1,(HANDLE)t2, (HANDLE)t3, (HANDLE)t4, (HANDLE)t5 };WaitForMultipleObjects(5, hArr, TRUE, INFINITE);printf("所有哲学家吃完!\n");return 0;
}
3、解决哲学家吃饭问题的另一种思路:按编号从小到大的顺序拿叉子
作如下规定:每个哲学家不按照“先拿左边的叉子再拿右边的叉子”的顺序拿叉子,而是“先拿编号小的叉子,再拿编号大的叉子”。如果按照这个规则,对于题目给出的哲学家和叉子的编号,0到3号哲学家都会先拿左边再拿右边,但4号哲学家相反,是先拿右边再拿左边。这样就可以避免死锁的情况发生。
//while(true)
//{
// 思考;
// 给编号小的叉子上锁;
// 给编号大的叉子上锁;
// 吃面条;
// 给编号小的叉子解锁;
// 给编号大的叉子解锁;
//}
#include<iostream>
#include<thread>
#include<mutex>
#include<ctime>using namespace std;
using namespace std::this_thread;
using namespace std::chrono;//用五个互斥量表示五支叉子
mutex fork[5];//用一个规模为5的整型数组来表示每个盘子剩余的面条
int nuddles[5] = { 50,50,50,50,50 };//吃面条函数
void eat(int id) {//id为哲学家编号while (true) {if (id < (id + 1) % 5) {fork[id].lock();fork[(id + 1) % 5].lock();printf("哲学家%d开始吃面条!\n", id + 1);srand((unsigned)time(NULL));int numbereated = rand() % (20 - 10 + 1) + 10;//sleep_for(seconds(numbereated % 10));//吃一段时间nuddles[id] -= numbereated;if (nuddles[id] <= 0) {printf(" 哲学家%d吃完了他的所有面条!\n", id + 1);fork[id].unlock();fork[(id + 1) % 5].unlock();break;}printf("哲学家%d吃完,面条剩余%d!\n", id + 1, nuddles[id]);fork[id].unlock();fork[(id + 1) % 5].unlock();sleep_for(milliseconds(200));//吃完之后歇一会,让其它哲学家能够吃上}else {fork[(id + 1) % 5].lock();fork[id].lock();printf("哲学家%d开始吃面条!\n", id + 1);srand((unsigned)time(NULL));int numbereated = rand() % (20 - 10 + 1) + 10;//sleep_for(seconds(numbereated % 10));//吃一段时间nuddles[id] -= numbereated;if (nuddles[id] <= 0) {printf(" 哲学家%d吃完了他的所有面条!\n", id + 1);fork[(id + 1) % 5].unlock();fork[id].unlock();break;}printf("哲学家%d吃完,面条剩余%d!\n", id + 1, nuddles[id]);fork[(id + 1) % 5].unlock();fork[id].unlock();sleep_for(milliseconds(200));//吃完之后歇一会(思考),让其它哲学家能够吃上}}
}int main()
{printf("开始吃饭!每人拥有50面条\n");thread t1(eat, 0);thread t2(eat, 1);thread t3(eat, 2);thread t4(eat, 3);thread t5(eat, 4);t1.join(); t2.join(); t3.join(); t4.join(); t5.join();printf("所有哲学家吃完!\n");return 0;
}
多线程同步——哲学家吃饭问题相关推荐
- linux多线程编写哲学家,Linux系统编程(三) ------ 多线程编程
一.线程的创建和调度 1.线程是程序执行的某一条指令流的映像. 为了进一步减少处理机制的空转时间,支持多处理器及减少上下文切换开销,进程在演化中出现了另一个概念--线程.它是进程内独立的一条运行路线, ...
- 用C语言多线程描述哲学家,C语言多线程之“哲学家就餐”问题
问题描述: 有五个哲学家,他们的生活方式是交替地进行思考和进餐.他们共用一张圆桌,分别坐在五张椅子上.在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左.右最靠近他的筷子,只有在 ...
- Linux C哲学家吃饭问题
哲学家吃饭问题 哲学家问题 线程中的信号量 无名信号量的定义: P操作:使信号量-1 V操作:使信号量+1 正常代码(可能发生死锁现象) 解法1 解法2 哲学家问题 有五个哲学家绕着圆桌坐,每个哲学家 ...
- 关于多线程同步与互斥
原文:看我稳住「多线程」翻车的现场!_小林coding-CSDN博客 文章目录 前言 正文 竞争与协作 互斥的概念 同步的概念 互斥与同步的实现和使用 锁 信号量 生产者-消费者问题(互斥+同步) 经 ...
- Java学习笔记---多线程同步的五种方法
一.引言 前几天面试,被大师虐残了,好多基础知识必须得重新拿起来啊.闲话不多说,进入正题. 二.为什么要线程同步 因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会 ...
- python多线程读取文件的问题_Python多线程同步---文件读写控制方法
1.实现文件读写的文件ltz_schedule_times.py #! /usr/bin/env python #coding=utf-8 import os def ReadTimes(): res ...
- 【转】windows平台多线程同步之Mutex的应用
线程组成: 线程的内核对象,操作系统用来管理该线程的数据结构. 线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量. 操作系统为每一个运行线程安排一定的CPU时间 -- 时间片.系统通过 ...
- java线程条件变量_多线程同步条件变量(转载)
最近看<UNIX环境高级编程>多线程同步,看到他举例说条件变量pthread_cond_t怎么用,愣是没有看懂,只好在网上找了份代码,跑了跑,才弄明白 #include #include ...
- MFC多线程同步互斥
MFC多线程同步互斥[转载] http://blog.sina.com.cn/s/blog_62d15fb601017dhn.html https://www.cnblogs.com/zhanghu5 ...
最新文章
- 隐马尔科夫模型(Hidden Markov Models) 系列之五
- 1.封装WinMain至动态链接库
- 反写规则-销售订单关闭后不允许出库 (销售订单-销售出库单)
- c# emnu 获取注释_C# 数据操作系列 - 19 FreeSql 入坑介绍
- 16.看板方法——三类改进机会笔记
- ParaFi Capital资产管理规模超1亿美元,至少投资22家公司或协议
- (cljs/run-at (JSVM. :all) 一次说白DataType、Record和Protocol) 1
- 第七届蓝桥杯省赛--四平方和
- 15-07-22 数据库--存储过程、触发器
- 《R数据可视化手册》——2.5 绘制箱线图
- Atitit 建设自己的财政体系 attilax总结 1.1. 收入理论	2 1.2. 收入分类	2 1.3. 	2 1.4. 非货币收入	2 1.5. 	2 1.6. 降低期望	2 1.7.
- 推荐8个实用精美的在线网站,珍藏多年!
- Java Map是否有序
- vsode 编译报错:main.c:4:10: fatal error: iostream: 没有那个文件或目录
- WEB端工程环境安装
- Java Runtime Environment (JRE) or Java Development Kit (JDK) must be available in order to run Ecli
- Startbbs YouBBS等轻论坛程序折腾过程
- 做报表到10点才下班,做的还是丑,怎样才能做出一张好看的报表?
- 单片机 并口 控制爱普生打印机开发流程记录
- 5分绩点转4分_5分制绩点换算成4分制(5.0绩点计算器在线)
热门文章
- Installation failed due to: ‘-26‘
- \t\tsizeof(char*)几个字节?
- [引爆流行]Meme Engine话题(一)
- c莫比乌斯函数_数论——容斥原理、莫比乌斯函数
- 行业音视频通信市场的技术发展
- hbase java编程,HBase编程实例
- 【a】标签的伪类选择器
- DotAsterisk(点星PBX)IPPBX V4.5在Hyper-V虚拟机中的安装
- 基于51单片机电热水壶自动加热水温控制系统(源程序+仿真+论文)
- 【小波滤波】基于小波变换的噪声信号滤波处理matlab仿真