最近在进行多线程编程的学习,学习过程中想起了复习操作系统的时候见过的“哲学家吃饭问题”。当时理解并不算透彻。于是就趁这个机会,利用最近所学模拟一下这个过程吧。

一、问题描述

二、问题分析

哲学家就餐问题需要协调考虑两个问题:

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;
}

多线程同步——哲学家吃饭问题相关推荐

  1. linux多线程编写哲学家,Linux系统编程(三) ------ 多线程编程

    一.线程的创建和调度 1.线程是程序执行的某一条指令流的映像. 为了进一步减少处理机制的空转时间,支持多处理器及减少上下文切换开销,进程在演化中出现了另一个概念--线程.它是进程内独立的一条运行路线, ...

  2. 用C语言多线程描述哲学家,C语言多线程之“哲学家就餐”问题

    问题描述: 有五个哲学家,他们的生活方式是交替地进行思考和进餐.他们共用一张圆桌,分别坐在五张椅子上.在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左.右最靠近他的筷子,只有在 ...

  3. Linux C哲学家吃饭问题

    哲学家吃饭问题 哲学家问题 线程中的信号量 无名信号量的定义: P操作:使信号量-1 V操作:使信号量+1 正常代码(可能发生死锁现象) 解法1 解法2 哲学家问题 有五个哲学家绕着圆桌坐,每个哲学家 ...

  4. 关于多线程同步与互斥

    原文:看我稳住「多线程」翻车的现场!_小林coding-CSDN博客 文章目录 前言 正文 竞争与协作 互斥的概念 同步的概念 互斥与同步的实现和使用 锁 信号量 生产者-消费者问题(互斥+同步) 经 ...

  5. Java学习笔记---多线程同步的五种方法

    一.引言 前几天面试,被大师虐残了,好多基础知识必须得重新拿起来啊.闲话不多说,进入正题. 二.为什么要线程同步 因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会 ...

  6. python多线程读取文件的问题_Python多线程同步---文件读写控制方法

    1.实现文件读写的文件ltz_schedule_times.py #! /usr/bin/env python #coding=utf-8 import os def ReadTimes(): res ...

  7. 【转】windows平台多线程同步之Mutex的应用

    线程组成: 线程的内核对象,操作系统用来管理该线程的数据结构. 线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量.   操作系统为每一个运行线程安排一定的CPU时间 -- 时间片.系统通过 ...

  8. java线程条件变量_多线程同步条件变量(转载)

    最近看<UNIX环境高级编程>多线程同步,看到他举例说条件变量pthread_cond_t怎么用,愣是没有看懂,只好在网上找了份代码,跑了跑,才弄明白 #include #include ...

  9. MFC多线程同步互斥

    MFC多线程同步互斥[转载] http://blog.sina.com.cn/s/blog_62d15fb601017dhn.html https://www.cnblogs.com/zhanghu5 ...

最新文章

  1. 隐马尔科夫模型(Hidden Markov Models) 系列之五
  2. 1.封装WinMain至动态链接库
  3. 反写规则-销售订单关闭后不允许出库 (销售订单-销售出库单)
  4. c# emnu 获取注释_C# 数据操作系列 - 19 FreeSql 入坑介绍
  5. 16.看板方法——三类改进机会笔记
  6. ParaFi Capital资产管理规模超1亿美元,至少投资22家公司或协议
  7. (cljs/run-at (JSVM. :all) 一次说白DataType、Record和Protocol) 1
  8. 第七届蓝桥杯省赛--四平方和
  9. 15-07-22 数据库--存储过程、触发器
  10. 《R数据可视化手册》——2.5 绘制箱线图
  11. Atitit 建设自己的财政体系 attilax总结 1.1. 收入理论 2 1.2. 收入分类 2 1.3. 2 1.4. 非货币收入 2 1.5. 2 1.6. 降低期望 2 1.7.
  12. 推荐8个实用精美的在线网站,珍藏多年!
  13. Java Map是否有序
  14. vsode 编译报错:main.c:4:10: fatal error: iostream: 没有那个文件或目录
  15. WEB端工程环境安装
  16. Java Runtime Environment (JRE) or Java Development Kit (JDK) must be available in order to run Ecli
  17. Startbbs YouBBS等轻论坛程序折腾过程
  18. 做报表到10点才下班,做的还是丑,怎样才能做出一张好看的报表?
  19. 单片机 并口 控制爱普生打印机开发流程记录
  20. 5分绩点转4分_5分制绩点换算成4分制(5.0绩点计算器在线)

热门文章

  1. Installation failed due to: ‘-26‘
  2. \t\tsizeof(char*)几个字节?
  3. [引爆流行]Meme Engine话题(一)
  4. c莫比乌斯函数_数论——容斥原理、莫比乌斯函数
  5. 行业音视频通信市场的技术发展
  6. hbase java编程,HBase编程实例
  7. 【a】标签的伪类选择器
  8. DotAsterisk(点星PBX)IPPBX V4.5在Hyper-V虚拟机中的安装
  9. 基于51单片机电热水壶自动加热水温控制系统(源程序+仿真+论文)
  10. 【小波滤波】基于小波变换的噪声信号滤波处理matlab仿真