转载

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

前几天看到一道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来讲,可以有很多方式实现。比如 objectwait/notifylock/conditionSemaphore 。这里我们讨论下这道题目如何用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 中先阻塞,然后第二个 goroutinechan 中写入然后在接收使其阻塞,第一个 goroutine 解除阻塞,然后继续写入解除第二个 goroutine 的阻塞,从而实现了交替打印.

扩展版实现

让N个协程交替打印1-100

这里我搜了下网上的实现方法,其中Golang让协程交替输出 这边的实现是利用了递归的特性,个人总感觉应该有更好的实现方式,在参考了java的 Semaphore 实现方式后,灵光一闪,可以利用 chan 的缓冲特性。大概的思路如下:

  1. 先新建N个缓冲为1的 chan ,同时往 chan 中写入一次数据(除去最后一个)
  2. 启动N个协程并编号
  3. 利用第1步中写入数据的操作,顺序阻塞 chan
  4. 判断是否满足退出条件,满足则退出,关闭 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 
复制代码
原文  https://juejin.im/post/5c98780af265da610a56f515
正文到此结束
Loading...