背景
前几天看到一道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),我们将立即处理。