Go中按次序交替打印1212...,你知道它背后的设计模式吗

124 阅读1分钟

聊聊后端面试那些事

让你goroutine交替打印1212...-【公粽号:堆栈future】

首先这道题看似是两个goroutine交替打印,实则是有很多细节需要考虑,而且背后的设计模式就是生产者消费者模式

代码演示下简单的生产者消费者模式:

package main

import "fmt"

func main() {
 done := make(chan bool//控制结束退出的信号
 ch := make(chan int//chan通信 控制同步阻塞的

 go func() {
  for i:=1; i<=2; i++ {
   //生产
   ch <- 0
   fmt.Println("1")
  }

  //发送结束之后退出
  done <- true
 }()

 go func() {
  for i:=1; i<=2; i++ {
   //消费
   <- ch
   fmt.Println("2")
  }
 }()
 <-done
}

输出:
1
1
2
2

但是题目是让两个goroutine交替输出1212...而且还必须保证次序,比如A goroutine是永远打印1,B goroutine永远打印2 这样循环2次,输出结果应该是1212。

那么为什么上面的代码输出是1122呢?因为我们控制了开始的次序,但是并没有控制结束的次序,发生了并发不安全的情况。

当然你不能说这种方式就不适合别的场景,比如交替打印奇偶数,就增加一个条件判断就行:

package main

import "fmt"

func main() {
 done := make(chan bool//控制结束退出的信号
 ch := make(chan int//chan通信 控制同步阻塞的

 go func() {
  for i:=1; i<=5; i++ {
   //生产
   ch <- 0
   if i % 2 == 0 {
    fmt.Println("A ", i)
   }
  }

  //发送结束之后退出
  done <- true
 }()

 go func() {
  for i:=1; i<=5; i++ {
   //消费
   <- ch
   if i % 2 != 0 {
    fmt.Println("B ", i)
   }
  }
 }()
 <-done
}

输出:
B  1
A  2
B  3
A  4

B永远输出奇数,A永远输出偶数,它们交替打印。

好了言归正传,那我们如何写出并发安全的生产者消费者代码呢?我们用首尾两个chan交替释放控制权就可以解决这个问题,请看代码:

package main

import "fmt"

func main() {
 done := make(chan bool//控制结束退出的信号
 chStart := make(chan int//chan通信 控制起始同步阻塞的
 chEnd := make(chan int//chan通信 控制结尾同步阻塞的

 go func() {
  for i:=1; i<=2; i++ {
   //生产 控制起始
   chStart <- 0
   fmt.Println("A "1)
   //暂停 等待结尾信号
   <-chEnd
  }

  //发送结束之后退出
  done <- true
 }()

 go func() {
  for i:=1; i<=2; i++ {
   //消费 结束起始
   <- chStart
   fmt.Println("B "2)

   //发送结尾信号
   chEnd <- 0
  }
 }()
 
 //阻塞等待退出信号
 <-done
}

输出:
A  1
B  2
A  1
B  2

现在可以看出来,输出的结果是正确的。为什么这么操作呢?就是当生产者生产之后把自己阻塞,等待消费者消费,消费者消费完成之后发送信号唤醒生产者继续生产,而消费者自己又被阻塞等生产者唤醒,其实这就是典型的生产者消费者模式。

小结

用两个chan实现按次序交替非常有意思,它背后的设计模式生产者消费者模式,大家可以在项目中使用起来,如果你觉得这篇文章对你有帮助,欢迎关注点赞转发哦。

- END -

公粽号:堆栈future

使很多处于迷茫阶段的coder能从这里找到光明,堆栈创世,功在当代,利在千秋

公粽号:堆栈future