一道阿里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來講,可以有很多方式實現。比如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 複製程式碼