要說Golang中最引以為傲的特性,那非goroutine莫屬,goroutine(協程)很輕量,相比於每個執行緒要使用1MB的記憶體,每個go協程只需要1kb左右就夠了。
於是,在需要做併發處理的時候,很自然的就想著,go一下就好了嗎? 示例程式碼如下
for i:=0; i < 5; i++ {
go func(index int) {
fmt.Println(index)
}(i) //這裡為什麼要把i傳進來呢?
}
這樣可以併發處理請求了是不假,但如果其中一個請求出錯了,需要退出怎麼辦了? 一方面,可以自己實現這個錯誤處理(稍後會寫),另一方面,也可以直接用golang官方errorgroup
上面的示例程式碼,如果用errorgroup來重新實現,會是下面這個樣子
g, _ := errgroup.WithContext(context.Background())
for i := 0; i < 5; i++ {
index := i
g.Go(func() error {
fmt.Println(index)
return nil // 如果想Mock一些錯誤,也可以return一個error
})
}
if err = g.Wait(); err != nil {
return err
}
是不是還挺簡單的?感興趣的,可以自行搜下原始碼,除去註釋只有大概30行程式碼,還是很好理解的。
現在錯誤處理也有了,是不是就完美了呢?
這個問題就要看你併發處理多少請求了,協程雖然很輕量,但也還是要耗費一些資源的,所以如果可以預見到有幾百上千的請求的要處理,那就需要協程池來複用協程,達到節省資源的目的了。
網上有很多協程池的實現,大都做的大而全,考慮了很多場景,但實際編碼場景中,很可能只是為了解決一個小問題,就引入一個包,實在覺得有些太重了呢,而且可能還不夠靈活。
有沒有一個簡單的模板,可以copy/paste/tweak一下呢?這就來了
閒話少絮,直接上程式碼先,關鍵部分會在程式碼中加註釋解釋。
var (
err error
outputs []int
workers = 4 //協程的數量,可以按需設定,一般不大於runtime.NumCPU()
workChannel = make(chan int)
errChannel = make(chan error, workers)
wg = &sync.WaitGroup{}
mux = sync.Mutex{}
)
worker := func(input int) (int, error){
retrun input, nil //如果想Mock一些錯誤,也可以return一個error
}
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
defer wg.Done()
for input := range workChannel { // workChannel被close時,這個迴圈就會退出
output, err:=worker(input)
if err != nil {
errChannel <- err
break
}
mux.Lock() //使用lock保護outputs,來蒐集執行結果,如果不需要可以刪除
outputs = append(outputs, output)
mux.Unlock()
}
}()
}
loop:
for _, input := range inputs {
select {
case workChannel <- input:
case err = <-errChannel:
break loop
}
}
close(workChannel) //關閉workChannel,可以讓工作協程,在處理完當前任務後退出
wg.Wait()
// 以於select case,如果有多個case滿足時,會選擇隨機進入一個case的,所以需要再檢查一次,雙重保險
if err == nil {
select {
case err = <-errChannel:
default:
}
}
return outputs, err
程式碼看著是多了些,但在實際使用過程中,按需要改下worker
函式輸入和輸出的型別即可。
如果copy/paste也不想做,那就只能封裝一下了,但是因為現在golang還沒正式推出範型,只能用inteface{}
了,看著是不大好看,使用時,也要自己轉來轉去的,不過可以湊合著用啦。
話說,封裝好的程式碼在這 parallel_runner.go,需要的自取了。
也許有人會問,為什麼不用context.WithCancel()
,然後在出現錯誤的時候cancel一下?
講究一點的話,確實應該用,但那也意味在在worker
函式中,你也要檢查ctx.Done()
, 我不用還是因為懶了……
之前還寫過一些關於channel的使用的文章,
裡面實現的輕量級util都開源在channelx,歡迎大家審閱,如果有你喜歡用的工具,歡迎點個贊或者star :)
本作品採用《CC 協議》,轉載必須註明作者和本文連結