前几天看到一道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 复制代码