选择遗忘|面试官:这个经典的并发问题用 Go 语言如何实现?( 二 )


这边我为了示范 goroutine , 先用最笨的碰运气解法 , 也就是不刻意做任何资源配置 , 要在运气很坏的情况下才会遇上 livelock 。 什么是「运气很坏的情况」?
就是所有哲学家刚好在同一时间拿起同一边的叉子 。 但实际上 , 由于我给每位哲学家一个随机的思考时间 50mS(如下列代码) , 碰撞的机会是(1/50)^5 , 所以绝大部分情况下不会发生 livelock 。
func Think() { Random := func(max int) int {rand.Seed(time.Now().Unix())return rand.Int() % (max + 1) } <-time.After(time.Millisecond * time.Duration(Random(50)))}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] <- philosopher: //尝试拿起左边筷子PickLeftFork(philosopher) //成功拿起左边筷子select {case this.streamForks[rightNum] <- philosopher: //尝试拿起右边筷子PickRightFork(philosopher)//成功拿起又边筷子Eat(philosopher)//左右边都拿到了 , 开始吃<-this.streamForks[leftNum] //吃完了 , 放下左边筷子PutLeftFork(philosopher)<-this.streamForks[rightNum] //吃完了 , 放下右边筷子PutRightFork(philosopher)return //吃饱离开default: //无法拿起右边筷子fmt.Printf("Philosopher %d can't pick fork %d.\n", philosopher, rightNum)


推荐阅读