背景
前幾天看到一道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 {
// 下一次在寫入,會阻塞住
chanSlice[i] <- 1
}
}
// 啟動同等數量的協程
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
}
複製程式碼
最終列印結果為:
goroutine0: 0
goroutine1: 1
goroutine2: 2
goroutine0: 3
goroutine1: 4
goroutine2: 5
goroutine0: 6
......
goroutine2: 98
goroutine0: 99
goroutine1: 100
複製程式碼