多线程之经典死锁场景及其解决(哲学家就餐问题)

字节觅云使
• 阅读 1891
  1. 编程练习-题目来源于宋劲杉《linuxc》
    哲学家就餐问题。这是由计算机科学家Dijkstra提出的经典死锁场景。

原版的故事里有五个哲学家(不过我们写的程序可以有N个哲学家),这些哲学家们只做两件事--思考和吃饭,他们思考的时候不需要任何共享资源,但是吃饭的时候就必须使用餐具,而餐桌上的餐具是有限的,原版的故事里,餐具是叉子,吃饭的时候要用两把叉子把面条从碗里捞出来。很显然把叉子换成筷子会更合理,所以:一个哲学家需要两根筷子才能吃饭。

现在引入问题的关键:这些哲学家很穷,只买得起五根筷子。他们坐成一圈,两个人的中间放一根筷子。哲学家吃饭的时候必须同时得到左手边和右手边的筷子。如果他身边的任何一位正在使用筷子,那他只有等着。

假设哲学家的编号是A、B、C、D、E,筷子编号是1、2、3、4、5,哲学家和筷子围成一圈如下图所示:
多线程之经典死锁场景及其解决(哲学家就餐问题)
图 35.2. 哲学家问题

每个哲学家都是一个单独的线程,每个线程循环做以下动作:思考rand()%10秒,然后先拿左手边的筷子再拿右手边的筷子(筷子这种资源可以用mutex表示),有任何一边拿不到就一直等着,全拿到就吃饭rand()%10秒,然后放下筷子。

编写程序仿真哲学家就餐的场景:

Philosopher A fetches chopstick 5
Philosopher B fetches chopstick 1
Philosopher B fetches chopstick 2
Philosopher D fetches chopstick 3
Philosopher B releases chopsticks 1 2
Philosopher A fetches chopstick 1
Philosopher C fetches chopstick 2
Philosopher A releases chopsticks 5 1
...

解决方案参考自https://blog.csdn.net/theLost...

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<malloc.h>
  4 #include<time.h>
  5 #include<unistd.h>
  6 #include<pthread.h>
  7 #include<semaphore.h>
  8 
  9 #define NUM 5
 10 
 11 sem_t chopsticks[NUM];//sem_t信号量参数,表示每根快筷子最多只能同时被一人拿起
 12 sem_t r;//拿起左筷子的信号量参数,只能同时有4人拿起左筷子,否则后产生死锁
 13 int philosophers[NUM] = {0,1,2,3,4};//哲学家数组,表示5位哲学家0,1,2,3,4,
 14 
 15 pthread_mutex_t chops[NUM];//互斥量即锁的控制变量
 16 
 17 //int Islocked[NUM] = {0};//自己实现互斥锁需要的控制变量数组
 18 //多个线程同时调用通常的swap函数的时候容易产生指令错排从而影响结果
 19 //而我们使用intel X86的指令集中提供了交换两数的指令xchg,寄存器控制不会出现并发的情况
 20 //void xchg(int *x,int *y){//汇编中的交换指令
 21 //    __asm__("xchgl %0, %1" : "=r" (*x) : "m" (*y));
 22 //}
 23 
 24 //核心函数(利用信号量控制最多4个人拿起左筷子来杜绝死锁的发生)
 25 void *philosopher(void *arg){//arg由init函数的第四个参数传递而来
 26     int i = *((int *)arg);//i代表第i个哲学家
 27     int left = i;//左边的筷子设为i
 28     int right = (i+1)%NUM;//循环队列结构,右边筷子设为i+1 
 29     //int leftkey;//自己实现左互斥锁需要的key变量
 30     //int rightkey;//自己实现右互斥锁需要的key变量
 31     while(1){
 32        //leftkey = 1;
 33        //rightkey = 1;
 34 
 35         printf("哲学家%d正在思考\n",i);
 36         sleep(rand()%NUM);//进程挂起一段时间,代表思考了一会
 37 
 38         printf("哲学家%d饿了\n",i);
 39 
 40         sem_wait(&r);//信号量r - 1(若r=0则挂起等待,r>0则可以获得线程资源执行后续操作,r不会<0)
 41         sem_wait(&chopsticks[left]);//注意每个chopsticks的信号量只有1个
 42         pthread_mutex_lock(&chops[left]);
 43         //do{
 44            //xchg(&leftkey,&Islocked[left]);//此时leftkey为非1,会将1交换给Islocked[left]
 45             //于是第二个线程进来时就会阻塞在do_while循环实现临界资源的锁定。即linux里面的mutex功能
 46         //}while(leftkey);
 47         printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n",i,left);
 48 
 49         //do{
 50             //xchg(&rightkey,&Islocked[right]);
 51         //}while(rightkey);
 52         sem_wait(&chopsticks[right]);
 53         pthread_mutex_lock(&chops[right]);
 54         printf("哲学家%d拿起了%d号筷子,现在只有两支筷子,开始进餐\n",i,right);
 55         sleep(rand()%NUM);//进程挂起一段时间,代表吃了一段时间结束
 56 
 57         //Islocked[left] = 0;//当前一个拿左筷子信号执行完就可以让第二个线程进入
 58         sem_post(&chopsticks[left]);
 59         pthread_mutex_unlock(&chops[left]);
 60         printf("哲学家%d放下了%d号筷子\n",i,left);
 61 
 62         //Islocked[right] = 0;//当前一个拿左筷子信号执行完就可以让第二个线程进入
 63         sem_post(&chopsticks[right]);
 64         pthread_mutex_unlock(&chops[right]);
 65         printf("哲学家%d放下了%d号筷子\n",i,right);
 66 
 67         sem_post(&r);//释放资源,信号量+1,于是可以同时唤醒挂起等待信号量>0的线程
 68 
 69     }
 70 }
 71 int main(int argc,char **argv){
 72     srand(time(NULL));
 73     pthread_t PHD[NUM];//要开辟的线程组
 74 
 75     int i= 0;
 76     for(i = 0;i < NUM; i++){
 77         sem_init(&chopsticks[i],0,1);//信号量初始化,每只筷子只能同时被拿起一次
 78     }
 79     sem_init(&r,0,4);//信号量控制变量r,控制同时拿起左筷子的信号不能超过4
 80 
 81     int j = 0;
 82     for(j = 0; j < NUM; j++){
 83         pthread_mutex_init(&chops[j],NULL);//互斥锁的初始化
 84     }
 85 
 86     int k = 0;
 87     for(k = 0; k < NUM; k++){
 88         pthread_create(&PHD[k],NULL,philosopher,&philosophers[k]);//创建5个哲学家的行为线程
 89     }
 90 
 91     int l = 0;
 92     for(l = 0; l < NUM; l++){
 93         pthread_join(PHD[l],NULL);//等待每个线程终止,将这些线程由终止态变为detach态并收回线程所占用的资源
 94     }
 95 
 96     int m = 0;
 97     for(m = 0; m < NUM; m++){
 98         sem_destroy(&chopsticks[m]);//释放变量所占用的信号量相关资源
 99     }
100     sem_destroy(&r);//同上,释放左筷子监控占用的信号量资源
101 
102     int n = 0;
103     for(n = 0; n < NUM; n++){
104         pthread_mutex_destroy(&chops[n]);//销毁互斥锁,释放资源
105     }
106 
107     return 0;
108 }

我这里是在linux环境下使用mutex实现,在windows环境下可以用注释掉的while-do-while循环来模拟实现互斥锁mutex,也能成功运行得到结果!
gcc philosopher_mutex.c -o philosopher_mutex -lpthread

./philosopher

多线程之经典死锁场景及其解决(哲学家就餐问题)

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Easter79 Easter79
3年前
sql注入
反引号是个比较特别的字符,下面记录下怎么利用0x00SQL注入反引号可利用在分隔符及注释作用,不过使用范围只于表名、数据库名、字段名、起别名这些场景,下面具体说下1)表名payload:select\from\users\whereuser\_id1limit0,1;!(https://o
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa