前言:由于 LeetCode Concurrency(并发) 还没有 Go 语言版本,我先自行用 Go 语言来解题。为了能在 LeetCode 以外的平台获得讨论,所以我打算逐渐把自己的解题思路写下。

本题 LeetCode 链接

https://leetcode.com/problems/the-dining-philosophers/

本题题目

「哲学家吃饭问题」是一个操作系统中的经典问题,所以抽象题干我就不再赘述,直接说实作要求。

The philosophers' ids are numbered from 0 to 4 in a clockwise order. Implement the function void wantsToEat(philosopher, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork) where:

有几位哲学家,他们的 ID 顺时针由 0~4,实作一个函式 void wantsToEat(philosopher, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork),其中...

philosopher is the id of the philosopher who wants to eat.

变量 philosopher 代表想要吃饭的哲学家的 ID。

pickLeftFork and pickRightFork are functions you can call to pick the corresponding forks of that philosopher.

变量 pickLeftFork and pickRightFork 是函式,你必须调用他们来使哲学家拿起对应的叉子。

eat is a function you can call to let the philosopher eat once he has picked both forks.

当哲学家拿起两只叉子后,你必须调用 eat 这个函式让哲学家吃一次。

putLeftFork and pickRightFork are functions you can call to put down the corresponding forks of that philosopher.

变量 putLeftFork and pickRightFork 是函式,你必须调用他们来使哲学家放下手中的叉子。

The philosophers are assumed to be thinking as long as they are not asking to eat (the function is not being called with their number).

假设哲学家们都会思考很久,中间都不会要求吃东西(调用函式 thinking() 不必使用哲学家们的 ID)

Five threads, each representing a philosopher, will simultaneously use one object of your class to simulate the process. It is possible that the function will be called for the same philosopher more than once, even before the last call ends.

五个执行绪,每一个执行绪代表都一个哲学家,用一个类(在 Go 语言是 struct)模拟这个 process。这个函式可能被同一个哲学家调用多次,甚至在最后一次调用结束前的途中都有可能。

「叉子」与「筷子」

最早课本里都是说「叉子」。但我大学上 OS 的时候老师就提过一个疑问:「用叉子吃义大利面,一只就够了,没必要用到两只吧?所以,改成用筷子是不是更合理一点?但没办法,谁叫这门学问是西方先发明的?我们就当作筷子吧」。
于是,本文也决定照改,以下都用「筷子」代替「叉子」。

本题考核难点?「拿得起放不下」造成死结、「无限轮回」造成活结饥饿至死

在过去的 LeetCode Concurrency 详解中,我提到过很多次:

goroutine 若不刻意控制,将无法保证执行的先后顺序,因此本题就是要考核对 goroutine 顺序控制的能力。

但前面几题的解法,大多是把判断责任中心化,方便控管顺序。这次,与前面几题不同的是,这一题要求把判断责任分散到每一位哲学家 thread 身上,哲学家彼此之间并不沟通,因此很容易发生资源互卡,也就是 deadlock。本文所示范的 channel 使用方法已经完全避免了死结(deadlock)。但这样就没问题了吗?不,还有可能发生活结(livelock)。

这边我为了示范 goroutine,先用最笨的碰运气解法,也就是不刻意做任何资源配置,要在运气很坏的情况下才会遇上 livelock。什么是「运气很坏的情况」?

就是所有哲学家刚好在同一时间拿起同一边的叉子。但实际上,由于我给每位哲学家一个随机的思考时间 50mS(如下列代码),碰撞的机会是(1/50)^5,所以绝大部分情况下不会发生 livelock。

func Think() { Random := func(max int) int {  rand.Seed(time.Now().Unix())  return rand.Int() % (max + 1) } 

Wiki 上有介绍不需要碰运气,保证不会让 thread 饥饿致死的演算法,但我自己也没搞懂,请容我日后再介绍。

解法与思路:

1. 所用 Channel 型态与定位?

本题采用 5 个 buffered channel,分别代表 5 支筷子

type DiningPhilosophers struct { wg                     *sync.WaitGroup streamForks            [5]chan interface{} missingDoubleForkTimes int}
// Channel 初始化for i := range diningPhilosophers.streamForks { diningPhilosophers.streamForks[i] = make(chan interface{}, 1)}

初始化

// 叫所有哲学家开始动作start := time.Now()for i := range diningPhilosophers.streamForks { diningPhilosophers.wg.Add(1) go diningPhilosophers.WantToEat(i, PickLeftFork, PickRightFork, Eat, PutLeftFork, PutRightFork)}

这边开始计时后,是一个 foreach。

老方法,用 sync.WaitGroup 同步 5 个哲学家 goroutine 结束时间。
给每一位哲学家起一个「WantToEat」的 goroutine,告诉他 i 你是几号?又给入「PickLeftFork, PickRightFork, Eat, PutLeftFork, PutRightFork」五个函式的的 function reference。

2. 五个 goroutine 之间,如何交接棒?

没有交接棒问题,每位哲学家就凭运气去抢左右边的两只筷子。
要注意的只有三件事情:

  1. 无法同时抢到两只筷子的哲学家,必须先放弃到手的一支筷子。
  2. 已经同时抢到两只筷子的哲学家,吃完就必须退出餐桌。
  3. 还没吃到的哲学家,可以无限次抢。

自循环 & 外部启动注意事项

这次解题没有实作这些协调机制,5 个 goroutine 只靠前述的三条规范野蛮生长。

实作前述的三条规范的 WantToEat()

  • 本质上就是代表「还没吃到的哲学家,可以无限次抢」的无限回圈。
  • 「已经同时抢到两只筷子的哲学家,吃完就必须退出餐桌」是此回圈的结束条件。
  • 「无法同时抢到两只筷子的哲学家,必须先放弃到手的一支筷子。」是此回圈其中一个分支。

有一行「return //吃饱离开」,整个流程最终目的就是要走到这一行。

func (this *DiningPhilosophers) WantToEat(philosopher int, pickLeftFork func(int), pickRightFork func(int), eat func(int), putLeftFork func(int), putRightFork func(int)) { defer this.wg.Done() var leftNum = (philosopher + 4) % 5  //取得该哲学家左边的号码 var rightNum = (philosopher + 6) % 5 //取得该哲学家右边的号码 for {  select {  case this.streamForks[leftNum] 

这边对于每一只筷子的具体表现就是一个 buffered channel,回圈流程如下:

  1. 先尝试把自己的号码塞入左边的 buffered channel
  2. 成功了,就是抢到一只筷子,往下。
  3. 失败了,跳到「default: //无法拿起左边筷子」,思考一下,然后从头开始。
  4. 再尝试把自己的号码塞入右边的 buffered channel
  5. 成功了,就是抢到两只筷子,开始吃,吃饱离开,退出餐桌。
  6. 失败了,跳到「default: //无法拿起右边筷子」,把已经抢到的左边筷子还回去,思考一下,然后从头开始。

在 console 输出,可以看到代表每一位哲学家的 goroutine 详细动作过程,错过筷子次数并不多,大部分执行结果的错过次数在 3~5 次(点击以下的「完整解题代码」就能体验)。

完整解题代码:

https://play.studygolang.com/p/neTH25E8ayX

示意图:

语言用加法实现加饭运算_面试官:这个经典的并发问题用 Go 语言如何实现?...相关推荐

  1. 语言用加法实现加饭运算_「编程之美」用C语言实现状态机(超实用)

    关于状态机,基础的知识点可以自行理解,讲解的很多,这里主要是想写一个有限状态机FSM通用的写法,目的在于更好理解,移植,节省代码阅读与调试时间,体现出编程之美. 传统的实现方案 if...else : ...

  2. C语言程序设计陆离明,《C语言程序设计》第十章 位运算_

    平政不迟囊瘤破裂长荣翘板夸诞旅行小碗?黔江密约布鞋秋高科甲骐骥狗剩裂片起始,闹区色彩电冶补台来货古香斥卤,迈普小学多长模态仿效!脖颈晾干两断水萍黄国嫔相漫骂? 门槛拉丝论点电法绿绕郎酒.莱锡赔垫魔族布 ...

  3. c语言中减号算一个字符吗,C语言中指针的加减运算

    char arr[3]; printf("arr:\n%d\n%d\n%d\n", arr, arr + 1, arr + 2); char *parr[3]; printf(&q ...

  4. c语言交换两个数字 位运算_交换两个8位数字| 8086微处理器

    c语言交换两个数字 位运算 Problem statement: 问题陈述: To swap two 8 bits numbers using third register on 8086 micro ...

  5. 干加个偏旁可以变成什么字_面试官:“干”字加一笔,变成什么字?回答王和午字不对...

    随着大学生的增多,如今的求职者进入职场,想到一份心仪的工作,最让人头疼的就是面试,越来越多的企业都需要全能型的人才,从而在面试的时候不仅要考核专业知识,面试官还要费尽心思出各种各样的题来考验求职者们的 ...

  6. jqgrid为什么表头和数据之间有间隙_面试官:你看过Redis数据结构底层实现吗?...

    面试中,redis也是很受面试官亲睐的一部分.我向在这里讲的是redis的底层数据结构,而不是你理解的五大数据结构.你有没有想过redis底层是怎样的数据结构呢,他们和我们java中的HashMap. ...

  7. 如何把class里的vector结构体memcpy出来_面试官:请说出线程安全的 ArrayList 有哪些,除了Vector...

    以下环境是 JDK 1.8 ArrayList 的初始容量 面试官:你看过 ArrayList 的源码? Python 小星:看过 面试官:那你说下ArrayList 的初始容量是多少? Python ...

  8. arraylist 后往前遍历_面试官:请说出线程安全的 ArrayList 有哪些,除了Vector

    以下环境是 JDK 1.8 ArrayList 的初始容量 面试官:你看过 ArrayList 的源码? Python 小星:看过 面试官:那你说下ArrayList 的初始容量是多少? Python ...

  9. 商品表有哪些字段_面试中有哪些经典的数据库问题?

    一.为什么用自增列作为主键 1.如果我们定义了主键(PRIMARY KEY),那么InnoDB会选择主键作为聚集索引.如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作为主 ...

最新文章

  1. BZOJ 1101 Luogu P3455 POI 2007 Zap (莫比乌斯反演+数论分块)
  2. 游戏设计模式思考:“穿越火线”中的“策略模式”
  3. SpringBoot_数据访问-整合Druid配置数据源监控
  4. Business Component(BC)和Business Object(BO)
  5. 看我如何用Dataphin实现自动化建模
  6. Handler sendMessage 与 obtainMessage (sendToTarget)比较
  7. android自动化必备之界面元素
  8. 如何使用以太网将 Mac 接入互联网?
  9. 白话空间统计三十:地统计学(2)前提假设
  10. 使用 JMeter 进行API接口压力测试
  11. requests库手工识别验证码登录超星泛雅
  12. Java 打开资源管理器
  13. GDAL 地图切片层级计算公式
  14. 真的有人能开启上帝视角,所有人在他眼里一览无余?
  15. vb.net 换行符的转换
  16. JSTL 计算时间差
  17. 风力发电机 有功功率 无功功率 理论有功功率
  18. 使用Arduino和HCSR04超声波传感器进行简单的超声波悬浮
  19. 视频教程-【CVPR2018】3D Pose Estimation and 3D Model Retriev-强化学习
  20. 太空上新 | 张肇达 张凯惠:嫦娥奔月有了现实版

热门文章

  1. php theme_path,PHP_Yii2主题(Theme)用法详解,本文实例讲述了Yii2主题(Theme) - phpStudy
  2. this和super的区别
  3. 用Python实现简单的人脸识别,10分钟(附源码)
  4. mysql 大事务 binlog_执行大事务时出现binlog解析失败
  5. 几种常见的攻击方式扫盲(二)——DNS 反射放大攻击
  6. Docker安全性支持(使用Cgroups机制实现容器资源控制)
  7. 【教程】1、加载静态内容
  8. python 问题不符合dcp rule_约束不遵循CVXPY中的DCP规则
  9. docker portainer_Docker入门详解(十一) 图形Portainer
  10. Ubuntu 16.04安装Zabbix 3.2 版本