一道阿里java多线程面试题的go版本实现

发表于:2019-3-26 09:35

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:S本尊    来源:掘金

  背景
  前几天看到一道java多线程面试题,题目很有意思,给人一种看起来简单却无从下手的感觉。原题目是这样的:
   通过N个线程顺序循环打印从0至100,如给定N=3则输出:
  thread0: 0
  thread1: 1
  thread2: 2
  thread0: 3
  thread1: 4
  .....
   相信很多朋友看到过它的精简版.
   两个线程交替打印0~100的奇偶数:
  偶线程:0
  奇线程:1
  偶线程:2
  奇线程:3
   
   两个线程交替打印数字和字母组合:
  偶线程:1
  奇线程:a
  偶线程:2
  奇线程:b
  对于java来讲,可以有很多方式实现。比如object的wait/notify、lock/condition和Semaphore。这里我们讨论下这道题目如何用go来实现。
  相信用go的小伙伴看到这道题目后首先想到的是用协程实现,不过go中并没有wait/notify这样的机制,goroutine是无法保证顺序的。我们先来实现这道题目的精简版本。
  精简版实现
  让两个协程交替打印1-100
   package main
  import "fmt"
  func main() {
  numChan := make(chan int)
  exitChan := make(chan struct{})
  go func() {
  for i := 1; i <= 101; i = i + 2 {
  result, ok := <-numChan
  if ok && result <= 100 {
  fmt.Println("goroutine0 : ", result)
  }
  numChan <- i
  }
  }()
  go func() {
  defer close(exitChan)
  for i := 0; i <= 100; i = i + 2 {
  numChan <- i
  result, ok := <-numChan
  if ok && result <= 100 {
  fmt.Println("goroutine1 : ", result)
  }
  }
  }()
  <-exitChan
  }
  这里我们利用了通道chan的阻塞性,在第一个goroutine中先阻塞,然后第二个goroutine往chan中写入然后在接收使其阻塞,第一个goroutine解除阻塞,然后继续写入解除第二个goroutine的阻塞,从而实现了交替打印.
  扩展版实现
  让N个协程交替打印1-100
  这里我搜了下网上的实现方法,其中Golang让协程交替输出 这边的实现是利用了递归的特性,个人总感觉应该有更好的实现方式,在参考了java的Semaphore实现方式后,灵光一闪,可以利用chan的缓冲特性。大概的思路如下:
  先新建N个缓冲为1的chan,同时往chan中写入一次数据(除去最后一个)
  启动N个协程并编号
  利用第1步中写入数据的操作,顺序阻塞chan
  判断是否满足退出条件,满足则退出,关闭chan
   func main() {
  // 要启动的协程数量
  coroutineNum := 3
  // 创建等同数量的chan,用于顺序传递
  chanSlice := make([]chan int, coroutineNum)
  // 监听退出chan
  exitChan := make(chan struct{})
  // 创建同等协程数的chan
  for i := 0; i < coroutineNum; i++ {
  chanSlice[i] = make(chan int, 1)
  if i != coroutineNum-1 {
  // 下一次在写入,会阻塞住
  go func(i int) {
  chanSlice[i] <- 1
  }(i)
  }
  }
  // 启动同等数量的协程
  for i := 0; i < coroutineNum; i++ {
  var lastChan chan int
  var curChan chan int
  // 注意这边,动态改变lastChan是为了控制协程的顺序
  // 可以理解为把编号1-N的chan,分配给编号1-N的goroutine
  // curChan代表下一个要执行的goroutine
  // lastChan代表要阻塞住当前那个goroutine
  // curChan对应goroutine的顺序为: 0->0,1->1,2->2
  // lastChan对应goroutine的顺序为: 2->0,0->1,1->2
  if i == 0 {
  lastChan = chanSlice[coroutineNum-1]
  } else {
  lastChan = chanSlice[i-1]
  }
  curChan = chanSlice[i]
  go func(i int, curChan chan int, lastChan chan int) {
  for {
  if result > 100 {
  close(exitChan)
  break
  }
  lastChan <- 1
  fmt.Printf("thread%d: %d \n", i, result)
  result = result + 1
  <-curChan
  }
  }(i, curChan, lastChan)
  }
  <-exitChan
  }
  最终打印结果为:
   goroutine2: 0
  goroutine0: 1
  goroutine0: 2
  goroutine1: 2
  goroutine1: 4
  goroutine2: 5
  goroutine0: 6
  goroutine1: 7
  goroutine2: 8
  ......
  goroutine2: 98
  goroutine0: 99
  goroutine1: 100
 
      上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号