通过哲学家进餐问题学习线程间协作(代码实现以leetcode1226为例)

2023-11-20

(代码实现以leetcode1226为例)

提到多线程和锁解决问题,就想到了os中哲学家进餐问题。

问题场景

回想该问题产生场景,五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替的进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。

解决思路

由于五位哲学家应相互独立,因此可以创建五个线程分别代表五位哲学家,相互独立(异步),依次编号为0~4。

对于资源(筷子),应该认为这是五种临界资源,而不是一种临界资源。因为每位哲学家只能使用自己面前的两个筷子。同样对五只筷子进行资源编号0~4,并且定义第i位哲学家左侧的筷子编号为i,哲学家右侧的筷子编号为(i+1)%5(这里要注意对编号为0的进行特殊处理)。

临界资源的定义,一次只允许一个线程使用的公共资源。所以当一只筷子被一位哲学家使用时应该加锁。

形成一种解决思路如下:

while(1){
    think()
    hungry()
    p(左筷子)
    p(右筷子)
    eat()
    v(左筷子)
    v(右筷子)
}

显而易见这种解决办法会出现死锁问题,即当每位哲学家获取左筷子后,永久陷入了等待右筷子状态,且每个人都不会主动放弃获取的临界资源。

解决死锁问题

①我们可以只允许四个哲学家都拿起同一边的筷子,这样会保证一位哲学家可以得到两边筷子完成进食,释放临界资源,保证别的哲学家可以进行相应的线程。

这里要新增一个信号量,控制拿起同一边筷子的哲学家数量。

修改后的解决思路如下:

while(1){
    think()
    hungry()
    p(还可以拿起一边筷子)
    p(左筷子)
    p(右筷子)
    eat()
    v(左筷子)
    v(拿起一边筷子的名额)
    v(右筷子)
}

②采用AND信号量机制,即当一个线程要执行时一次性将所需的资源全部给他,否则不分给他资源。

新的解决思路如下:

while(1){
	think()
	hungry()
	Swait(左筷子,右筷子)
	eat()
	Spost(左筷子,右筷子)
}

针对不用的代码语言,当不支持and信号量机制时,可以考虑增加新的互斥信号量来实现。全局互斥信号量,当一个哲学家企图拿筷子时候,就将全部资源锁住。

修改后的解决思路如下:

while(1){
	think()
	hungry()
	p(lock)
	p(左筷子)
    p(右筷子)
    v(lock)
    eat()
    v(左筷子)
    v(右筷子)
}

③区分奇数偶数哲学家,规定奇数号哲学家先拿起他左边的筷子,然后再去拿他右边的筷子,而偶数号的哲学家则相反,这样的话总能保证一个哲学家能获得两根筷子完成进餐,从而释放其所占用的资源。

解决思路如下:

while(1){
	think(i)
	hungry(i)
	if(i % 2 == 0){
        p(左筷子)
        p(右筷子)
        eat()
        v(右筷子)
        v(左筷子)
    }
    else{
    	p(右筷子)
    	p(左筷子)
        eat()
        v(左筷子)
        v(右筷子)
    }
}

代码实现

c++

①只允许四个哲学家都拿起同一边的筷子 room信号量4

#include<semaphore.h>
class DiningPhilosophers {
public:
    DiningPhilosophers() {
        fork = vector<sem_t>(5);
        sem_init(&room,0,4);
        for(int i = 0;i < 5;i++){
            sem_init(&(fork[i]),0,1);
        }
    }

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) {
        sem_wait(&room);
        sem_wait(&(fork[philosopher]));
        sem_wait(&(fork[(philosopher+1)%5]));
        pickLeftFork();
        pickRightFork();
        eat();
        putRightFork();
        putLeftFork();
        sem_post(&(fork[(philosopher+1)%5]));
        sem_post(&(fork[philosopher]));
        sem_post(&room);
    }
    vector<sem_t> fork;
    sem_t room;
};

②AND信号量方式

#include<semaphore.h>
class DiningPhilosophers {
public:
    DiningPhilosophers() {
        fork = vector<sem_t>(5);
        for(int i = 0;i < 5;i++){
            sem_init(&(fork[i]),0,1);
        }
        sem_init(&mutex,0,1);
    }

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) {
        sem_wait(&mutex);
        sem_wait(&(fork[philosopher]));
        sem_wait(&(fork[(philosopher+1)%5]));
        pickLeftFork();
        pickRightFork();
        sem_post(&mutex);
        eat();
        putRightFork();
        putLeftFork();
        sem_post(&(fork[(philosopher+1)%5]));
        sem_post(&(fork[philosopher]));
    }
    vector<sem_t> fork;
    sem_t mutex;
};

③奇偶数

#include<semaphore.h>
class DiningPhilosophers {
public:
    DiningPhilosophers() {
        fork = vector<sem_t>(5);
        for(int i = 0;i < 5;i++){
            sem_init(&(fork[i]),0,1);
        }
    }

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) {
        if(philosopher % 2 == 0){
            sem_wait(&(fork[philosopher]));
            sem_wait(&(fork[(philosopher+1)%5]));
            pickLeftFork();
            pickRightFork();
            eat();
            putRightFork();
            putLeftFork();
            sem_post(&(fork[(philosopher+1)%5]));
            sem_post(&(fork[philosopher]));
        }
        else{
            sem_wait(&(fork[(philosopher+1)%5]));
            sem_wait(&(fork[philosopher]));
            pickLeftFork();
            pickRightFork();
            eat();
            putRightFork();
            putLeftFork();
            sem_post(&(fork[philosopher]));
            sem_post(&(fork[(philosopher+1)%5]));
        }
    }
    vector<sem_t> fork;
};
go

奇偶数

package main

import (
	"fmt"
	"sync"
)

type DiningPhilosophers struct {
	getkey    []chan int
	endsSgnal *sync.WaitGroup
}

func pickLeftFork(i int) {
	fmt.Printf("philosopher %d pickLeftFork\n", i)
}
func pickRightFork(i int) {
	fmt.Printf("philosopher %d pickRightFork\n", i)
}
func eat(i int) {
	fmt.Printf("philosopher %d eat\n", i)
}
func putLeftFork(i int) {
	fmt.Printf("philosopher %d putLeftFork\n", i)
}
func putRightFork(i int) {
	fmt.Printf("philosopher %d putRightFork\n", i)
}

func (s *DiningPhilosophers) wantsToEat(philosopher int, pickLeftFork func(int), pickRightFork func(int), eat func(int), putLeftFork func(int), putRightFork func(int)) {
	if philosopher%2 == 0 {
		<-s.getkey[philosopher]
		pickLeftFork(philosopher)
		<-s.getkey[(philosopher+1)%5]
		pickRightFork(philosopher)

		eat(philosopher)

		putRightFork(philosopher)
		s.getkey[(philosopher+1)%5] <- 1
		putLeftFork(philosopher)
		s.getkey[philosopher] <- 1
	} else {
		<-s.getkey[(philosopher+1)%5]
		pickRightFork(philosopher)
		<-s.getkey[philosopher]
		pickLeftFork(philosopher)

		eat(philosopher)

		putLeftFork(philosopher)
		s.getkey[philosopher] <- 1
		putRightFork(philosopher)
		s.getkey[(philosopher+1)%5] <- 1
	}
	s.endsSgnal.Done()
}

func main() {
	s := &DiningPhilosophers{
		getkey:    make([]chan int, 5),
		endsSgnal: &sync.WaitGroup{},
	}
	for i := 0; i < len(s.getkey); i++ {
		s.getkey[i] = make(chan int, 1)
		s.getkey[i] <- 1
	}
	n := 100
	for i := 0; i < n; i++ {
		s.endsSgnal.Add(5)
		go s.wantsToEat(0, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork)
		go s.wantsToEat(1, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork)
		go s.wantsToEat(2, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork)
		go s.wantsToEat(3, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork)
		go s.wantsToEat(4, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork)
	}
	s.endsSgnal.Wait()
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

通过哲学家进餐问题学习线程间协作(代码实现以leetcode1226为例) 的相关文章

  • 访问私人成员[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过将类的私有成员转换为 void 指针 然后转换为结构来访问类的私有成员是否合适 我认为我无权修改包含我需要访问的数据成员的类 如果不道德 我
  • Qt-Qlist 检查包含自定义类

    有没有办法覆盖加载自定义类的 Qt QList 的比较机制 即在 java 中你只需要重写一个比较方法 我有一个带有我的自定义类模型的 QList QList
  • 当我使用“control-c”关闭发送对等方的套接字时,为什么接收对等方的套接字不断接收“”

    我是套接字编程的新手 我知道使用 control c 关闭套接字是一个坏习惯 但是为什么在我使用 control c 关闭发送进程后 接收方上的套接字不断接收 在 control c 退出进程后 发送方的套接字不应该关闭吗 谢谢 我知道使用
  • 将数组向左或向右旋转一定数量的位置,复杂度为 o(n)

    我想编写一个程序 根据用户的输入 正 gt 负 include
  • Newtonsoft JSON PreserveReferences处理自定义等于用法

    我目前在使用 Newtonsoft Json 时遇到一些问题 我想要的很简单 将要序列化的对象与所有属性和子属性进行比较以确保相等 我现在尝试创建自己的 EqualityComparer 但它仅与父对象的属性进行比较 另外 我尝试编写自己的
  • 为什么#pragma optimize("", off)

    我正在审查一个 C MFC 项目 在某些文件的开头有这样一行 pragma optimize off 我知道这会关闭所有以下功能的优化 但这样做的动机通常是什么 我专门使用它来在一组特定代码中获得更好的调试信息 并在优化的情况下编译应用程序
  • C - 找到极限之间的所有友好数字

    首先是定义 一对友好的数字由两个不同的整数组成 其中 第一个整数的除数之和等于第二个整数 并且 第二个整数的除数之和等于第一个整数 完美数是等于其自身约数之和的数 我想做的是制作一个程序 询问用户一个下限和一个上限 然后向他 她提供这两个限
  • 如何将图像和 POST 数据上传到 Azure 移动服务 ApiController 终结点?

    我正在尝试上传图片and POST表单数据 尽管理想情况下我希望它是json 到我的端点Azure 移动服务应用 我有ApiController method HttpPost Route api upload databaseId sea
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • Qt moc 在头文件中实现?

    是否可以告诉 Qt MOC 我想声明该类并在单个文件中实现它 而不是将它们拆分为 h 和 cpp 文件 如果要在 cpp 文件中声明并实现 QObject 子类 则必须手动包含 moc 文件 例如 文件main cpp struct Sub
  • 如何返回 json 结果并将 unicode 字符转义为 \u1234

    我正在实现一个返回 json 结果的方法 例如 public JsonResult MethodName Guid key var result ApiHelper GetData key Data is stored in db as v
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • Qt表格小部件,删除行的按钮

    我有一个 QTableWidget 对于所有行 我将一列的 setCellWidget 设置为按钮 我想将此按钮连接到删除该行的函数 我尝试了这段代码 它不起作用 因为如果我只是单击按钮 我不会将当前行设置为按钮的行 ui gt table
  • 从库中捕获主线程 SynchronizationContext 或 Dispatcher

    我有一个 C 库 希望能够将工作发送 发布到 主 ui 线程 如果存在 该库可供以下人员使用 一个winforms应用程序 本机应用程序 带 UI 控制台应用程序 没有 UI 在库中 我想在初始化期间捕获一些东西 Synchronizati
  • 当操作繁忙时,表单不执行任何操作(冻结)

    我有一个使用 C 的 WinForms 应用程序 我尝试从文件中读取一些数据并将其插入数据表中 当此操作很忙时 我的表单冻结并且无法移动它 有谁知道我该如何解决这个问题 这可能是因为您在 UI 线程上执行了操作 将文件和数据库操作移至另一个
  • 如何在 VBA 中声明接受 XlfOper (LPXLOPER) 类型参数的函数?

    我在之前的回答里发现了问题 https stackoverflow com q 19325258 159684一种无需注册即可调用 C xll 中定义的函数的方法 我之前使用 XLW 提供的注册基础结构 并且使用 XlfOper 类型在 V
  • 在 Dynamics CRM 插件中访问电子邮件发件人地址

    我正在编写一个 Dynamics CRM 2011 插件 该插件挂钩到电子邮件实体的更新后事件 阶段 40 pipeline http msdn microsoft com en us library gg327941 aspx 并且在此阶
  • WCF:将随机数添加到 UsernameToken

    我正在尝试连接到用 Java 编写的 Web 服务 但有些东西我无法弄清楚 使用 WCF 和 customBinding 几乎一切似乎都很好 除了 SOAP 消息的一部分 因为它缺少 Nonce 和 Created 部分节点 显然我错过了一
  • 为什么我收到“找不到编译动态表达式所需的一种或多种类型。”?

    我有一个已更新的项目 NET 3 5 MVC v2 到 NET 4 0 MVC v3 当我尝试使用或设置时编译出现错误 ViewBag Title财产 找不到编译动态表达式所需的一种或多种类型 您是否缺少对 Microsoft CSharp
  • 使用 libcurl 检查 SFTP 站点上是否存在文件

    我使用 C 和 libcurl 进行 SFTP FTPS 传输 在上传文件之前 我需要检查文件是否存在而不实际下载它 如果该文件不存在 我会遇到以下问题 set up curlhandle for the public private ke

随机推荐

  • 桌面怎么设置 计算机 网络连接,电脑桌面的本地连接ip地址可以设置吗_本地连接ip地址设置方法 - 驱动管家...

    1 首先在Win7桌面上找到 网络 入口 如下图 进入Win7网络 2 进入网络之后我们再点击顶部的 网络共享中心 如下图 进入Win7网络共享中心 3 进入Win7网络共享中心之后 我们再点击左侧的 更改网络适配器 如下图 选择更改网络适
  • 经典,一文讲透ESD原理和设计

    一直想给大家讲讲ESD的理论 很经典 但是由于理论性太强 任何理论都是一环套一环的 如果你不会画鸡蛋 注定了你就不会画大卫 先来谈静电放电 ESD Electrostatic Discharge 是什么 这应该是造成所有电子元器件或集成电路
  • Ubuntu20.04通过rsync和inotify实现定时备份与实时备份

    通过rsync和inotify实现定时备份与实时备份 为了避免主服务单点故障 可以将数据备份到远程备份机器 可以使用rsync工具同步Jenkins home到远程 可以利用rsync工具的 exclude from FILE 功能 定制一
  • KVM热迁移

    KVM热迁移 介绍 KVM热迁移的前提是拥有共享存储 以下通过NFS实现KVM热迁移 迁移过程 将一处于运行状态的KVM虚拟机从节点kvm 01迁移到kvm 02后继续运行 准备 主机准备 hostname IP地址 系统 配置 kvm 0
  • Docker 国内镜像地址

    http f1361db2 m daocloud io http hub mirror c 163 com https docker mirrors ustc edu cn
  • c++中的类模板

    C 的类模板为生成通用的类声明提供了一种更好的方法 模板提供参数化类型 即能够将类型名作为参数传递给接收方来建立类或者函数 一 定义类模板 include
  • IndexError: index 5 is out of bounds for axis 1 with size 5

    keras中报错 IndexError index 5 is out of bounds for axis 1 with size 5 原因 大概率是你的数据集label没有设置好 keras中数据集标签需要从0开始 并且连续 类似于下图这
  • Unity WebGL错误集锦

    ips 0 Unity的PlayerSettings的otherSettings或者Publish Settings里面的Enable Exceptions里面选择Full StackTrace 可以在打出的包中的浏览器的webgl打印出错
  • 【计算机基础

    定点数的表示 定点数 小数点的位置固定 例 996 007 常规计数 浮点数 小数点的位置不固定 例 9 96007 10 2 科学计数法 二进制的定点数 浮点数也类似 无符号数 整个机器字长的全部二进制位均为数值位 没有符号位 相当于数的
  • 关于Linux和Shell的相关书籍

    入门类 一直认为 在一个系统上学习开发之前 首先需要熟悉这个系统的使用 鉴于天朝的国情 绝大部分人第一个接触的操作系统就是Windows 因此对于这绝大部分人来说 如果要学习Linux开发 学会使用这个系统都是必不可少的一个环节 现在的Li
  • UVa 1347 Tour

    题目 Tour 题意 来自luogu John Doe想用最小的路程游览完所有目的地 每个目的地都用坐标xi yi表示 任何两目的地的xi都不相同 两目的地之间的路程是两点之间的直线距离 John是这样走的 他从最左边的点开始 然后只能向右
  • word页码如何设置为章节加页码,例如第一章第一页1-1、第二章第一页2-1

    由于用到word页码分章节 页码的形式 从网上查了一下 质量真的很差 没有一篇文章讲清楚的 有的所答非所问 一怒之下 利用几个小时的时间解决问题并写下这篇文章 以供大家学习参考 1 word插入页码 选择包含章节号 1 1 双击页脚 点击插
  • 55黑马QT笔记之关闭子线程

    55黑马QT笔记之关闭子线程 1 这里为什么要单独写多一篇文章来说线程的关闭呢 主要是想让大家提升印象 养成资源回收的好习惯 任何时候都要想起开辟过的内存回收 这里的关闭子线程上一篇也写到了 就是利用关闭窗口时调用槽函数回收掉 2 具体步骤
  • 2023最新ChatGPT网站源码+支持GPT4+Ai绘画+用户会员套餐+邀请分佣功能+支持后台一键更新+永久更新!

    2023最新ChatGPT网站源码 支持GPT4 Ai绘画 用户会员套餐 邀请分佣功能 支持后台一键更新 永久更新 可同时 单独 开启或者关闭GPT3 5和GPT4 0两种ChatGPT提问模型 用户可切换 次数套餐也是分开的 支持手机电脑
  • News Feed 系统设计

    新鲜事系统 News Feed 什么是新鲜事 News Feed 你登陆 Facebook Twitter 朋友圈 之后看到的信息流 你的所有朋友发的信息的集合 有哪些典型的新鲜事系统 Facebook Twitter 朋友圈 RSS Re
  • Windows与Linux系统实现文件互传(通俗易懂)

    SCP指令可以实Windows系统与Linux系统之间的文件互传 引言 Windows系统文件传输到Linux系统上 先操作 Windows系统文件传输到Linux系统上 再细聊 Linux系统文件传输到Windows系统上 先操作 Lin
  • 趁着周日我卷了 uni-app《uview 狠 优秀的UI框架》

    前期回顾 手写一个服务器代码将 vue电商后台管理系统 部署上去 上线 打包 活在风浪里的博客 CSDN博客亲测可用 一定会收获颇多 1 上线vue电商后台管理项目2 手写搭建服务器并挂载 node 3 打包优化 完成上线https blo
  • Shell数组:shell数组的定义、数组长度

    Shell在编程方面比Windows批处理强大很多 无论是在循环 运算 bash支持一维数组 不支持多维数组 并且没有限定数组的大小 类似与C语言 数组元素的下标由0开始编号 获取数组中的元素要利用下标 下标可以是整数或算术表达式 其值应大
  • QGIS插件式开发(一)---PyQt5+python3.6+Pychram2017.3开发环境配置

    1 PyQt简介 PyQt是用来创建GUI应用程序的工具包 它把python和Qt成功地绑定在一起 Qt库是目前最强大的库之一 PyQt是由Phil Thompson开发 PyQt实现了一个Python模块集 它有超过300个类 将近600
  • 通过哲学家进餐问题学习线程间协作(代码实现以leetcode1226为例)

    哲学家进餐问题 代码实现以leetcode1226为例 问题场景 解决思路 解决死锁问题 代码实现 c go 代码实现以leetcode1226为例 提到多线程和锁解决问题 就想到了os中哲学家进餐问题 问题场景 回想该问题产生场景 五个哲