哲学家就餐问题

文章目录:

  • 问题描述及分析
  • 一次错误的尝试
  • 解决方案一
  • 解决方案二

问题描述及分析

哲学家就餐问题规定了有5位哲学家正在进行思考和就餐两种活动。用餐在一个桌子上进行,桌子上面有5个盘子和5个叉子,按照循环的方式分配。

在这里插入图片描述

问题的约束条件:

  • 每个哲学家需要两支筷子才能就餐;
  • 每个哲学家可以从他的左边或者右边拿起筷子,但是一次只能拿一支;
  • 哲学家只有在拿到两支筷子时才能就餐。我们必须设计一个协议,即事前和事后协议,确保哲学家只有拿到两支筷子才能够就餐;
  • 每支筷子可以是拿起和放下的。

解决哲学家就餐问题的方案需要满足以下条件:

  • 互斥原则:每个哲学家在任何时刻只能与左右两边的筷子之一进行交互,即一次只能拿起一支筷子。
  • 无饥饿问题:哲学家在尝试获取筷子时,若那支筷子已经被其它哲学家持有,则需要等待该筷子可以使用。需要避免某个哲学家等待长时间无法获取筷子。
  • 避免死锁:死锁指每个哲学家都在等待筷子,导致无法继续就餐的情况。
  • 合理利用时间:合理利用时间,使得每个哲学家都能够在适当的时间获得就餐的机会,而不是长期等待或浪费时间。

一次错误的尝试

下列代码使用信号量来实现临界区的互斥访问,通过循环让哲学家不停的思考和进餐。semaphore fork[5] = {1, 1, 1, 1, 1}; 初始化了一个包含5个元素的信号量数组,初始值为1,表示该筷子可用。

如下所示,给出伪代码:

semaphore fork[5] = {1, 1, 1, 1, 1};

void philosopher(int i)
{
    do
    {
        // thinking...
        wait(fork[i]);           // 等待左边的筷子
        wait(fork[(i + 1) % 5]); // 等待右边的筷子
        // eat
        signal(fork[i]);           // 放下左边的筷子
        signal(fork[(i + 1) % 5]); // 放下右边的筷子
    } while (true);                // 无限循环,不断进行思考和进餐
}

该解决方案存在的问题:

该解决方案可能在某种交叉执行的情况下导致死锁,即当所有哲学家在尝试拿起右边的筷子之前都先拿起了左边的筷子时,就会发生死锁。在这种情况下,所有的哲学家都在等待右边的筷子而阻塞,但没有一个人会执行一条指令。

解决方案一

这种解决方案是只允许有四位哲学家同时拿起左边的筷子,我们可以增加一个信号量来实现,通过该信号量来限制哲学家并发进程的数量。

semaphore fork[5] = {1, 1, 1, 1, 1}; // 初始化叉子的信号量,初始值为1表示叉子可用
semaphore count = 4;                 // 控制最多允许四位哲学家同时进餐的信号量

void philosopher(int i)
{
    do
    {
        // thinking...
        wait(count);             // 判断是否超过四人准备进餐,若超过则等待
        wait(fork[i]);           // 等待左边的叉子可用
        wait(fork[(i + 1) % 5]); // 等待右边的叉子可用
        // eat...
        signal(fork[i]);           // 释放左边的叉子,使其可用
        signal(fork[(i + 1) % 5]); // 释放右边的叉子,使其可用
        signal(count);             // 用餐完毕,别的哲学家可以开始进餐
    } while (true);                // 循环,使哲学家不断进行思考和进餐的过程
}

该算法通过使用信号量来控制叉子的获取和释放,避免了死锁的发生。每个哲学家在进餐前会先判断是否超过最大允许同时进餐的数量,如果超过则需要等待。通过这种方式,保证了最多只有四位哲学家同时进餐。在每次进餐时,哲学家会依次获取左边和右边的叉子,进餐完毕后释放叉子,使其可供其他哲学家使用。该算法保证哲学家之间的竞争是有序的,避免了死锁的发生。

但是,这种算法依旧存在一些问题。在某些情况下,可能回出现饥饿问题,即某个哲学家一直无法获取两个筷子而无法就餐。

解决方案二

接下来尝试使用非对称算法,其中第五位哲学家与前四位哲学家的行为不同。该解决方案使用了信号量(semaphore)和监视器(monitor)两种方式来实现。

为每支筷子初始化一个信号量数组 fork,初始化值为1,表示筷子可用。

semaphore fork[5] = {1, 1, 1, 1, 1};

对于前四位哲学家:

semaphore fork[5] = {1, 1, 1, 1, 1};

void philosopher(int i)
{
    do
    {
        // thinking...
        wait(fork[i]);
        wait(fork[(i + 1) % 5]);
        // eat
        signal(fork[i]);
        signal(fork[(i + 1) % 5]);
    } while (true);
}

对于第五位哲学家:

semaphore fork[5] = {1, 1, 1, 1, 1};

void philosopher(int i)
{
    do
    {
        // thinking...
        wait(fork[0]); // 等待右边的筷子可用
        wait(fork[4]); // 等待左边的筷子可用
        // eat
        signal(fork[0]); // 释放右边的筷子
        signal(fork[4]); // 释放左边的筷子
    } while (true);
}

该方案有以下优势:

  • 允许较大程度的并发性;
  • 避免饥饿;
  • 避免死锁;
  • 更灵活;
  • 公平性;
  • 有界性;

算法伪代码:这段代码使用了监视器ForkMonitor来管理筷子的状态和哲学家的行为。fork数组记录了每个筷子的可用数量,OKtoEat条件变量数组用于等待哲学家能够进餐的条件。

takeForks操作用于哲学家获取筷子。如果左右筷子中有一个不可用,哲学家会等待相应的条件变量。一旦筷子都可用,哲学家将获取左右筷子。

releaseForks操作用于哲学家释放筷子。哲学家将释放左右筷子,并检查相邻筷子是否可用。如果相邻筷子可用,则发出相应的条件变量信号,以通知等待的哲学家。

monitor ForkMonitor:
    integer array[0..4] fork ← [2, 2, 2, 2, 2]
    condition array[0..4] OKtoEat

    operation takeForks(integer i):
        // 如果左右叉子有一个不可用,则等待
        if fork[i] != 2:
            waitC(OKtoEat[i])

        // 获取左右叉子
        fork[i + 1] ← fork[i + 1] - 1
        fork[i - 1] ← fork[i - 1] - 1

    operation releaseForks(integer i):
        // 释放左右叉子
        fork[i + 1] ← fork[i + 1] + 1
        fork[i - 1] ← fork[i - 1] + 1

        // 如果相邻叉子可用,则发出信号
        if fork[i + 1] == 2:
            signalC(OKtoEat[i + 1])

        if fork[i - 1] == 2:
            signalC(OKtoEat[i - 1])

下面是 c++ 中使用监视器实现的哲学家就餐问题代码(基于Linux系统):

  • 下列实现能获得最大的并行度。其中使用一个数组 state_ 来跟踪一个哲学家的状态(思考、吃饭、试图拿筷子(饥饿))。
  • 一个哲学家只有在左右两个邻居哲学家都没有进餐的情况下才能进入进餐状态。第 i 位哲学家的邻居由宏 LEFTRIGHT 定义。
  • 该方案使用了一个信号量数组,每个信号量分别对应一个哲学家。当所需的筷子被占用时,想进餐的哲学家可以阻塞。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

// 哲学家数量
#define PNUM 5
// 哲学家的三种状态
#define THINKING 2
#define STARVATION 1
#define EATING 0
// 根据哲学家索引计算其左右哲学家索引
#define LEFT (phnum + 4) % PNUM
#define RIGHT (phnum + 1) % PNUM

int index[PNUM]; // 存储哲学家的索引
int times = 200; // 哲学家就餐的次数

// 定义一个监视器类
class Monitor
{
public:
    Monitor()
    {
        for (int i = 0; i < PNUM; i++)
        {
            state_[i] = THINKING;
            pthread_cond_init(&phCond_[i], nullptr);
        }
        pthread_mutex_init(&mutex_, nullptr);
    }

    ~Monitor()
    {
        for (int i = 0; i < PNUM; i++)
            pthread_cond_destroy(&phCond_[i]);
        pthread_mutex_destroy(&mutex_);
    }

    // 检查是否满足就餐条件
    void test(int phnum)
    {
        if (state_[(phnum + 1) % PNUM] != EATING && state_[(phnum + PNUM - 1) % PNUM] != EATING && state_[phnum] == STARVATION)
        {
            state_[phnum] = EATING;
            pthread_cond_signal(&phCond_[phnum]);
        }
    }

    // 哲学家拿筷子
    void takeFork(int phnum)
    {
        pthread_mutex_lock(&mutex_);
        state_[phnum] = STARVATION;
        // 检查条件是否满足,不满足则等待
        test(phnum);
        if (state_[phnum] != EATING)
            pthread_cond_wait(&phCond_[phnum], &mutex_);
        cout << "Philosopher " << phnum + 1 << " is Eating." << endl;
        pthread_mutex_unlock(&mutex_);
    }

    // 哲学家放下筷子
    void putFork(int phnum)
    {
        pthread_mutex_lock(&mutex_);
        state_[phnum] = THINKING;
        // 检查旁边哲学家是否可以就餐
        test(LEFT);
        test(RIGHT);
        pthread_mutex_unlock(&mutex_);
    }

private:
    int state_[PNUM];             // 哲学家状态
    pthread_cond_t phCond_[PNUM]; // 条件变量
    pthread_mutex_t mutex_;       // 互斥锁
};

Monitor philObject;

void *philosopher(void *args)
{
    int cnt = 0;
    while (cnt < times)
    {
        int i = *(int *)args;
        sleep(1);
        philObject.takeFork(i);
        sleep(0.5);
        philObject.putFork(i);
        cnt++;
    }
    return nullptr;
}

int main()
{
    pthread_t threadID[PNUM];
    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for (int i = 0; i < PNUM; i++)
        index[i] = i;

    for (int i = 0; i < PNUM; i++)
    {
        pthread_create(&threadID[i], &attr, philosopher, &index[i]);
        cout << "Philosopher " << i + 1 << "is thinking..." << endl;
    }

    for (int i = 0; i < PNUM; i++)
        pthread_join(threadID[i], nullptr);

    pthread_attr_destroy(&attr);
    pthread_exit(nullptr);
    return 0;
}

运行结果:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/191197.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据中台】开源项目(2)-Wormhole流式处理平台

Wormhole 是一个一站式流式处理云平台解决方案&#xff08;SPaaS - Stream Processing as a Service&#xff09;。 Wormhole 面向大数据流式处理项目的开发管理运维人员&#xff0c;致力于提供统一抽象的概念体系&#xff0c;直观可视化的操作界面&#xff0c;简单流畅的配置管…

【React】Memo

组件重新渲染时缓存计算的结果。 实例&#xff1a;count1计算斐波那契数列&#xff0c;count2和count1可以触发数值变化。使用memo可以使只有在count1变化时触发斐波那契数列计算函数&#xff0c;而count2变化时不触发斐波那契数列计算函数。 import { useMemo } from "r…

二十六、搜索结果处理(排序、分页、高亮)

目录 一、排序 二、分页 1、深度分页问题 2、三种方案的优缺点 &#xff08;1&#xff09;fromsize 优点&#xff1a; 缺点&#xff1a; 场景&#xff1a; &#xff08;2&#xff09;after search 优点&#xff1a; 缺点&#xff1a; 场景&#xff1a; &#xff0…

git的使用:本地git下载、sshkey的添加、github仓库创建及文件上传

一、github创建账号 即github注册账号&#xff0c;登录github官网&#xff0c;根据提示注册即可 github官网 二、git客户端下载安装 已有很多git下载安装的博文了&#xff0c;在此就不赘述 三、sshkey的生成与添加 1、sshkey的生成以及查看 // sshkey的生成命令&#xff…

【代码】考虑电解槽变载启停特性与阶梯式碳交易机制的综合能源系统优化调度matlab-yalmip-cplex/gurob

程序名称&#xff1a;考虑电解槽变载启停特性与阶梯式碳交易机制的综合能源系统优化调度 实现平台&#xff1a;matlab-yalmip-cplex/gurobi 代码简介&#xff1a;提出了一种考虑 变载启停特性的电解槽混合整数线性模型&#xff0c;根据电 氢负荷可以实时调整设备工作状态&…

Leetcode211. 添加与搜索单词 - 数据结构设计

Every day a Leetcode 题目来源&#xff1a;211. 添加与搜索单词 - 数据结构设计 解法1&#xff1a;字典树 字典树&#xff08;前缀树&#xff09;是一种树形数据结构&#xff0c;用于高效地存储和检索字符串数据集中的键。前缀树可以用 O(∣S∣) 的时间复杂度完成如下操作…

Linux进程通信——信号量

概念 信号量(semaphore) 与已经介绍过的 PC 结构不同&#xff0c;它是一个计数器。信号量用于实现进程间的互斥与同步&#xff0c;而不是用于存储进程间通信数据。 特点 1.信号量用于进程间同步&#xff0c;若要在进程间传递数据需要结合共享内存 2.信号量基于操作系统的 PV…

VUE简易计划清单

目录 效果预览图 完整代码 效果预览图 完整代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>…

嵌入式的学习需要合理规划时间

低级的欲望放纵即可获得&#xff0c;高级的欲望只有克制才能达成。——卡耐基1、粉丝的误会 很多粉丝&#xff0c;问我&#xff0c; "胡老师我想报您的培训班。" ... 得知我知识业余时间写文章&#xff0c;紧接着又会问&#xff0c; "jg单位这么清闲啊&#…

粉丝提问:写博文怎样才能变现啊?

文章目录 粉丝提问&#xff1a;写博文怎样才能变现啊&#xff1f;我总结了一下博客变现的几个途径&#xff1a;另外做技术博主的五大好处 后记 粉丝提问&#xff1a;写博文怎样才能变现啊&#xff1f; type: Post status: Published date: 2023/11/26 tags: 推荐 category…

单调栈类型题

搞定八道高频算法题 一、如何找右边第一个比我小的元素 二、如何找右边第一个比我大的元素 三、如何找右边最后一个比我小的元素 四、如何找右边最后一个比我大的元素 五、如何找左边第一个比我小的元素 六、如何找左边第一个比我大的元素 七、如何找左边最后一个比我小的元素 …

Nginx常见的中间件漏洞

目录 1、Nginx文件名逻辑漏洞 2、Nginx解析漏洞 3、Nginx越权读取缓存漏洞 这里需要的漏洞环境可以看&#xff1a;Nginx 配置错误导致的漏洞-CSDN博客 1、Nginx文件名逻辑漏洞 该漏洞利用条件有两个&#xff1a; Nginx 0.8.41 ~ 1.4.3 / 1.5.0 ~ 1.5.7 php-fpm.conf中的s…

泛型你掌握多少?包装类你深入了解过吗?快进来看看吧~

目录 1、泛型是什么——引出泛型 2、泛型的使用 2.1、语法 2.2泛型类的使用 2.3、裸类型 3、泛型如何编译 3.1、擦除机制 3.2、为什么不能实例化泛型类型数组 4、泛型的上界 5、泛型方法 5.1、语法 5.2、举例 6、通配符 6.1、什么是通配符 6.2、统配符解决了什么…

【数据中台】开源项目(2)-Dbus系统架构

大体来说&#xff0c;Dbus支持两类数据源&#xff1a; RDBMS数据源 日志类数据源 1 RMDBMS类数据源的实现 以mysql为例子. 分为三个部分&#xff1a; 日志抽取模块(最新版DBus已经废弃该模块&#xff0c;使用canal直接输出到kafka) 增量转换模块 全量拉取模块 1.1 日志抽…

单片机学习4——中断的概念

中断的概念&#xff1a; CPU在处理A事件的时候&#xff0c;发生了B事件&#xff0c;请求CPU迅速去处理。&#xff08;中断产生&#xff09; CPU暂时中断当前的工作&#xff0c;转去处理B事件。&#xff08;中断响应和中断服务&#xff09; 待CPU将B事件处理完毕后&#xff0…

深入理解JVM虚拟机第二十六篇:详解JVM当中的虚方法和非虚方法,并从字节码指令的角度去分析虚方法和非虚方法

😉😉 学习交流群: ✅✅1:这是孙哥suns和树哥给大家的福利! ✨✨2:我们免费分享Netty、Dubbo、k8s、Spring...应用和源码级别的视频资料 🥭🥭3:QQ群:583783824 📚​​​​​​​📚 微信:DashuDeveloper拉你进微信群,免费领取! 一:非虚方法和虚方法 方法…

【JAVA杂货铺】一文带你走进面向对象编程|继承|重载|重写|期末复习系列 | (中4)

&#x1f308;个人主页: Aileen_0v0&#x1f525;系列专栏:Java学习系列专栏&#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 继承 私有成员变量在继承中的使用​编辑 当子类和父类变量不重名时: 当子类和父类重名时: &#x1f4dd;总结: 继承的含义: …

网络视频播放卡顿原因分析

一、问题描述 某项目通过拉摄像机rtsp流转rtmp/http-flv/ws-flv的方案&#xff0c;使用户可以在网页中观看摄像机的视频画面。在 观看视频时偶发出现卡顿现象。 二、卡顿现象分析和解决 此问题涉及的原因较多&#xff0c;所以得考虑各环节的问题可能性&#xff0c;并根据现场实…

Vue常见的实现tab切换的两种方法

目录 方法一&#xff1a;事件绑定属性绑定 效果图 完整代码 方法二&#xff1a;属性绑定 动态组件 component标签 效果图 完整代码 方法一&#xff1a;事件绑定属性绑定 效果图 完整代码 <!DOCTYPE html> <html lang"en"> <head><meta c…

5.前端--CSS-基本概念【2023.11.26】

1. CSS 语法规范 CSS 规则由两个主要的部分构成&#xff1a;选择器以及一条或多条声明。 属性和属性值之间用英文“:”分开 多个“键值对”之间用英文“;”进行区分 选择器 : 简单来说&#xff0c;就是选择标签用的。 声明 &#xff1a;就是改变样式 2.CSS引入方式 按照 CSS 样…