一道阿里java多執行緒面試題的go版本實現

S本尊發表於2019-03-25

背景

前幾天看到一道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來講,可以有很多方式實現。比如objectwait/notifylock/conditionSemaphore。這裡我們討論下這道題目如何用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中先阻塞,然後第二個goroutinechan中寫入然後在接收使其阻塞,第一個goroutine解除阻塞,然後繼續寫入解除第二個goroutine的阻塞,從而實現了交替列印.

擴充套件版實現

讓N個協程交替列印1-100

這裡我搜了下網上的實現方法,其中Golang讓協程交替輸出 這邊的實現是利用了遞迴的特性,個人總感覺應該有更好的實現方式,在參考了java的Semaphore實現方式後,靈光一閃,可以利用chan的緩衝特性。大概的思路如下:

  1. 先新建N個緩衝為1的chan,同時往chan中寫入一次資料(除去最後一個)
  2. 啟動N個協程並編號
  3. 利用第1步中寫入資料的操作,順序阻塞chan
  4. 判斷是否滿足退出條件,滿足則退出,關閉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 
複製程式碼

相關文章