前几天看到一道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
是无法保证顺序的。我们先来实现这道题目的精简版本。
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
的阻塞,从而实现了交替打印.
这里我搜了下网上的实现方法,其中Golang让协程交替输出 这边的实现是利用了递归的特性,个人总感觉应该有更好的实现方式,在参考了java的 Semaphore
实现方式后,灵光一闪,可以利用 chan
的缓冲特性。大概的思路如下:
chan
,同时往 chan
中写入一次数据(除去最后一个) 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 复制代码