背景

今天商汤面试 golang 岗位, 面试官没有按照常规出算法题, 而是出了道 go routine 交替打印的题目。由于没有准备相关的代码题, 大概率凉凉。特此记录一下。

事后复盘, 这道题还是相当坑的, 要是没有准备, 或者是 golang 并发编程不是特别精通, 短时间是想不到的。 很多人一开始想到的是 开两个 go routine , go routine 里面不停地读 一个 channel, 根据 channel 里面读出来的内容, 来决定在哪个 go routine 里面打印, go routine 打印完以后继续向 channel 里面写, 另一个 go routine 接着读。 比如 协程A读出来是0 ,则协程A 打印, 接着协程A往 channel写入1, 协程B 读出来是1 则 第协程B 打印, 接着往channel 写入0。这样做看起来是不错的, 但问题在于 : 从读出来以后, 如何确保 两个 go routine 交替往 go routine 写, 交替从 go routine 读

看到这你可能蒙蔽了,对啊,如果用 channel 去向两个 协程传递信号, 如何确保交替性呢。

事实上当你想到用 1个channel 去 通知两个 协程的时候读的时候就已经掉进坑里了。

正确的做法

用两个 channel。协程A 从 chan 1 里面读, 协程A读完以后 往 chan 2 里面写, 协程B 再从 chan 2 里面读,读完再往 chan1 里面写。这里 chan1 一定要用带缓冲的 channel, 因为 chan1 被读以后不会立马有 协程来写,而是要等待协程 A 打印以后 写入 chan2, 协程B 读完 chan2 以后打印, 才会去写 chan2

package main

import (
   "fmt"
   "sync"
)


func main() {

   wg := sync.WaitGroup{};
   c1 := make(chan int,1);
   c2 := make(chan int);
   wg.Add(2);

   go func() {
      defer  wg.Done()
      for i:=0; i<10 ; i++  {
         <- c1
         fmt.Println("A")
         c2<-1
      }
   }()


   go func() {
      defer  wg.Done()
      for i:=0; i<10 ; i++  {
         <-c2
         fmt.Println("B")
         c1<-1
      }
   }()



   c1 <-1;
   wg.Wait()
}
复制代码

聪明的同学可能会问了,要是让你轮流在3个协程里面打印输出, 你会怎么办呢? 答案很简单, 和方法2 页码一模一样,用三个 chan, 三个协程轮流读channel, 写 channel,形成一个环。 同样第一个 chan 要用带缓冲的。

package main

import (
   "fmt"
   "sync"
)


func main() {

   wg := sync.WaitGroup{};
   c1 := make(chan int,1);
   c2 := make(chan int);
   c3 := make(chan int);
   wg.Add(3);

   go func() {
      defer  wg.Done()
      for i:=0; i<10 ; i++  {
         <- c1
         fmt.Println("A")
         c2<-1
      }
   }()


   go func() {
      defer  wg.Done()
      for i:=0; i<10 ; i++  {
         <-c2
         fmt.Println("B")
         c3<-1
      }
   }()

   go func() {
      defer  wg.Done()
      for i:=0; i<10 ; i++  {
         <-c3
         fmt.Println("C")
         c1<-1
      }
   }()

   c1 <-1;
   wg.Wait()
}
复制代码