并行程序设计第4次作业——哲学家就餐问题

一、问题描述

五个沉默的哲学家围坐在一个圆桌旁,桌上放着几碗意大利面。叉子被放置在每一对相邻的哲学家之间。每个哲学家必须交替地思考和进餐。然而,哲学家只能在有左右叉子的情况下才能吃意大利面。每个叉子只能被一个哲学家拿着,所以一个哲学家只能在没有被另一个哲学家使用的情况下使用它。当一位哲学家吃完饭后,他们需要把两把叉子放下,这样其他人就可以享用这些叉子了。哲学家只能在他们有空的时候拿右手或左手的叉子,在拿两副叉子之前他们不能开始吃东西。每个哲学家有3种状态{thinking,trying,eating}。吃东西不受剩余的意大利面或胃空间的限制,假设有一个无限的供给和一个无限的需求。问题是如何设计一种行为准则(并发算法),以确保没有哲学家挨饿;也就是说,每一种都可以永远在吃饭和思考之间交替,假设没有一个哲学家可以知道其他人什么时候可能想吃饭或思考。

二、概要设计

编程语言使用IEEE Posix Thread库与C语言实现。本次实验采用多线程解决哲学家就餐问题。由于哲学家的刀叉是共享变量,所以使用信号量实现互斥。线程个数(-n)即为哲学家个数,同时为每个叉子定义对应的信号量。每个哲学家在试图使用餐叉的时候需要先进行sem_wait,当拿到两个餐叉后,哲学家就能eating,eating完之后使用sem_post解锁这两个餐叉。

三、避免死锁的策略

method1:

解法:允许最多 thread_count - 1 个哲学家同时进入餐厅尝试获取餐叉,剩下的一个哲学家只有在有人吃完出去的时候才进去。这样就能保证起码有一个人能够同时获得两个餐叉,从而eating,避免大家都处于trying的状态。这种解法也能理解为引入一个餐厅服务生,哲学家必须经过他的允许才能拿起餐叉。当同时有 thread_count - 1 个餐叉被请求时,若是其他哲学家请求最后一个餐叉,服务生会让他等待。

实现方法:增加一个信号量sem_waiter,其初始值为thread_count - 1,每个线程trying时会先sem_wait(&sem_waiter),在请求到两个餐叉并且eating后,再解锁两个叉子并使用sem_post(&sem_waiter)通知服务生。

method2:

解法:使用非对称解决方案。即单号的哲学家先拿起左边的叉子,接着右边的叉子;而双号的哲学家先拿起右边的叉子,接着左边的叉子。

实现方法:获取当前线程的编号,如果是奇数号线程就先请求左边的餐叉,然后请求右边的餐叉;如果是偶数好线程就先请求右边的餐叉,然后请求左边的餐叉。

四、实验

编译:gcc -g -Wall -o philosopher philosopher.c -lpthread

运行:./philosopher -normal n 哲学家个数 ./philosopher -method1 n 哲学家个数 ./philosopher -method2 n 哲学家个数

五、分析

1. -normal

正常状态下哲学家遵守以下规则:

哲学家在左边的叉子可用(没有其他人拿起)之前处于思考状态。如果左边的叉子可用,就拿起来。

哲学家等待右边的叉子可用。如果右边的叉子可用,就拿起来。

如果两个叉子都已经拿起来,开始吃意大利面,每次吃面都花费同样的时间。

吃完后先放下左边的叉子。

然后放下右边的叉子。

开始思考(进入一个循环)

但这种解法是失败的,当每个哲学家都拿起左侧的叉子,等待右侧的叉子可用时,就会进入死锁状态,每个哲学家将永远都在等待(右边的)另一个哲学家放下叉子。

2. -method1

至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。以下将sem_waiter作为信号量,只允许thread_count - 1个哲学家同时进入餐厅就餐,这样就能保证至少有一个哲学家可以就餐,而申请进入餐厅的哲学家进入sem_waiter的等待队列,根据FIFO的原则,总会进入到餐厅就餐,因此不会出现饿死和死锁的现象。

3.-method2

规定奇数号的哲学家先拿起他左边的筷子,然后再去拿他右边的筷子;而偶数号的哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子,3、4号哲学家竞争3号筷子。即五个哲学家都竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一个哲学家能获得两支筷子而进餐。而申请不到的哲学家进入阻塞等待队列,根FIFO原则,则先申请的哲学家会较先可以吃饭,因此不会出现饿死的哲学家。

六、源代码

#include

#include

#include

#include

#include

#include

long thread_count; //线程个数,即哲学家个数

sem_t *sem_fork; //定义一组餐叉的信号量

sem_t sem_waiter; //method1中用到的信号量,用于确保至少一个人不能进餐

void *Philo_normal(void *rank); //一般方法,会陷入死锁

void *Philo_method1(void *rank); //方法1,服务生方法

void *Philo_method2(void *rank); //方法2,非对称解决方案

int main(int argc, char *argv[])

{

long thread;

pthread_t *thread_handles;

//获取线程个数,本实验中也代表哲学家人数

thread_count = strtol(argv[2], NULL, 10);

thread_handles = malloc (thread_count * sizeof(pthread_t));

sem_fork = malloc (thread_count * sizeof(sem_t));

//初始化waiter信号量

sem_init(&sem_waiter, 0, thread_count-1);

//初始化所有叉子信号量

for(long i = 0; i < thread_count; i ++ ){

sem_init(&sem_fork[i], 0, 1);

}

//创建线程

for(thread = 0; thread < thread_count; thread++ ){

//根据参数进入相应的处理函数

if(strcmp(argv[1], "-normal") == 0)

pthread_create(&thread_handles[thread], NULL, Philo_normal, (void *)thread);

else if(strcmp(argv[1], "-method1") == 0)

pthread_create(&thread_handles[thread], NULL, Philo_method1, (void *)thread);

else if(strcmp(argv[1], "-method2") == 0)

pthread_create(&thread_handles[thread], NULL, Philo_method2, (void *)thread);

}

//停止线程

for(thread = 0; thread < thread_count; thread ++ )

pthread_join(thread_handles[thread], NULL);

//销毁餐叉信号量

for(long i = 0; i < thread_count; i ++ ){

sem_destroy(&sem_fork[i]);

}

//销毁服务生信号量

sem_destroy(&sem_waiter);

//释放空间

free(thread_handles);

free(sem_fork);

return 0;

}

//一般方法,会陷入死锁

void *Philo_normal(void *rank)

{

long my_rank = (long)rank;

long my_left_fork, my_right_fork; //表示每个哲学家左右的叉子

my_left_fork = my_rank;

my_right_fork = (my_rank + 1) % thread_count;

while(1){

printf("Philosopher %ld is thinking\n", my_rank);

//把进程挂起一段时间, 单位是微秒(百万分之一秒);

//usleep( (rand()%91 + 10)*1000 );

//本来应该随机挂起10-100ms,这里为了更快的检测死锁,将时间改为固定的50ms

usleep(50);

printf("Philosopher %ld is trying\n", my_rank);

sem_wait(&sem_fork[my_left_fork]);

sem_wait(&sem_fork[my_right_fork]);

printf("Philosopher %ld is eating\n", my_rank);

//usleep( (rand()%91 + 10)*1000 );

usleep(100);

sem_post(&sem_fork[my_left_fork]);

sem_post(&sem_fork[my_right_fork]);

}

}

//方法1,服务生方法

void *Philo_method1(void *rank)

{

long my_rank = (long)rank;

//哲学家左右的叉子

long my_left_fork, my_right_fork;

my_left_fork = my_rank;

my_right_fork = (my_rank + 1) % thread_count;

while(1){

printf("Philosopher %ld is thinking\n", my_rank);

//usleep( (rand()%91 + 10)*1000 ); //think 10-100 ms

usleep(50);

printf("Philosopher %ld is trying\n", my_rank);

//添加一个waiter信号量确保每个时刻最少一个人没有进去就餐

sem_wait(&sem_waiter);

sem_wait(&sem_fork[my_left_fork]);

sem_wait(&sem_fork[my_right_fork]);

printf("Philosopher %ld is eating\n", my_rank);

//usleep( (rand()%91 + 10)*1000 ); //eating 10-100 ms

usleep(100);

sem_post(&sem_fork[my_left_fork]);

sem_post(&sem_fork[my_right_fork]);

sem_post(&sem_waiter);

}

}

//方法2,非对称解决方案

void *Philo_method2(void *rank)

{

long my_rank = (long)rank;

//表示哲学家左右的叉子

long my_left_fork, my_right_fork;

//奇数先左叉后右叉,偶数先右叉后左叉

if(my_rank % 2 == 1){

my_left_fork = (my_rank + 1) % thread_count;

my_right_fork = my_rank;

}

else{

my_left_fork = my_rank;

my_right_fork = (my_rank + 1) % thread_count;

}

while(1){

printf("Philosopher %ld is thinking\n", my_rank);

//usleep( (rand()%91 + 10)*1000 ); //think 10-100 ms

usleep(50);

printf("Philosopher %ld is trying\n", my_rank);

sem_wait(&sem_fork[my_left_fork]);

sem_wait(&sem_fork[my_right_fork]);

printf("Philosopher %ld is eating\n", my_rank);

//usleep( (rand()%91 + 10)*1000 ); //eating 10-100 ms

usleep(100);

sem_post(&sem_fork[my_left_fork]);

sem_post(&sem_fork[my_right_fork]);

}

}

七、参考

用C语言多线程描述哲学家,并行程序设计-哲学家就餐问题相关推荐

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

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

  2. 奥鹏20春季1903C语言,奥鹏20春学期《并行程序设计》在线作业

    1,n个数求和的串行程序,通过一个循环将每个数累加到全局变量sum中,其多线程版本简单将循环范围改变为每 个线程负载的范围,存在的问题是____. A 负载不均 B 通信开销大 C CPU空闲等待严重 ...

  3. linux线程并不真正并行,多核时代:并行程序设计探讨(3)——Windows和Linux对决(多进程多线程)...

    并行程序设计探讨(3)--Windows和Linux对决(多进程多线程) 前面的博文经过分析总结,最后得出两种并行技术:多进程多线程.多机协作.对于多进程和多线程来说,最有代表性且最常见的的莫过于Wi ...

  4. c语言mpi并行程序,高性能计算之并行编程技术MPI并行程序设计(完整版).pdf

    高性能计算之并行编程技术MPI并行程序设计(完整版) 高性能计算之并行编程技术 -- MPI并行程序设计 都志辉 编著 李三立 审阅 陈渝 刘鹏 校对 I 内容提要 本书介绍目前最常见的并行程序- M ...

  5. c语言 mpi程序设计,MPI并行程序设计-基础

    有两门课的作业要用到MPI,"高性能计算环境"和"并行与分布式系统",所以简单了解了一下MPI的基础知识.MPI并不是并行程序设计的唯一方法,但却是一种行之有效 ...

  6. 并行程序设计——OMP编程

    并行程序设计--OMP编程 实验一 实验内容 分别实现课件中的梯形积分法的Pthread.OpenMP版本,熟悉并掌握OpenMP编程方法,探讨两种编程方式的异同. 实验代码 OpenMP编程 #in ...

  7. 并行程序设计报告(MPI并行计算π,实现mandelbrot集)

    代码的github地址 一.熟悉MPI并行程序设计环境 1.硬件 电脑:HP暗夜精灵 内存:4G 处理器:ntel® Core™ i5-6300HQ CPU @ 2.30GHz × 4 显卡:NVID ...

  8. 基于mpi的奇偶排序_并行程序设计(第2版)pdf

    并行程序设计(第2版) 内容简介 本书系统介绍并行程序设计原理及应用.除介绍常用的一些算法范例,包括分治.流水.同步计算.主从及工作池,还介绍了一些常用的经典数值和非数值算法,如排序.矩阵相乘.线性方 ...

  9. 实战matlab之并行程序设计.pdf,实战Matlab之并行程序设计_IT教程网

    资源名称:实战Matlab之并行程序设计 内容简介: <实战Matlab之并行程序设计>通过阅读和学习,读者可以掌握基于多种平台(多核.多处理器.集群和GPU等),利用多项技术(Matla ...

  10. linux下c语言线程传参数,【linux】C语言多线程中运行线程池,在线程池中运行线程池,,传递的结构体参数值为空/NULL/0...

    C语言多线程中运行线程池,在线程池中运行线程池,,传递的结构体参数值为空/NULL/0 本贴问题,之前已经提问过一次,当时已经解决了,原贴在这里https://segmentfault.com/q/1 ...

最新文章

  1. 什么是OpenMAX技术分析OpenMAX
  2. opencv函数总结
  3. 专业音频如何把电平转换成dbu_这是我见过最细致的音频系统增益设置指南,跟着学起来!...
  4. 329. Longest Increasing Path in a Matrix 矩阵中的最长递增路径
  5. 我的crystal report for asp.net測試通過了
  6. android 百度移动搜索 url 参数,百度刷站内快排算法参数-百度搜索URL参数比较详解...
  7. C#表格文字多收缩样式
  8. 中单引号怎么打出来_怎么做打出来的豆浆会更好?
  9. 面对SDN,我们该怎么办?
  10. ISO12233:2014 eSFR分辨率测试卡使用方法
  11. 通达信交易服务器修改,GitHub - sjj6love/TdxTradeServer: TongDaXin Tarde Server 通达信交易服务器...
  12. Android免费离线文字转语言+讯飞语记
  13. 教你如何关闭Surface Go的触摸键盘?
  14. Amazon Alexa硬件方案选型
  15. 【问链-区块链基础知识系列】 第十二课 区块链产业落地现状分析
  16. 华为路由交换课程笔记10-GARP和GVRP
  17. 如何利用Syncthing+蒲公英快速实现异地文件同步
  18. word没有显示endnote_word没有endnote加载项 endnote word加载项
  19. 关于React18控制台报错:ReactDOM.render is no longer supported in React 18 Use createRoot instead. Until.....
  20. c语言while根据近似公式求,c语言用π=1-1/3+1/5-1/7+.公式求π的近似值,直到最后一项的绝对值小于10^-6为止...

热门文章

  1. ensp(华为VRRP配置)
  2. 一键获取系统特殊权限 - TrustedInstaller权限,可以直接修改hosts等系统文件
  3. 网上图片的几种保存方法
  4. layabox 节点查找
  5. 软件测试工作基本流程
  6. AmazeUI 图标的示例代码
  7. word另存为pdf后,pdf文件中有空白页
  8. IPv4如何转换为IPv6?
  9. 施耐德驱动器维修ELAU控制器维修C400C600
  10. LabVIEW2018工具包分享