goroutine
模擬了執行緒級別的返場的能力,但它的執行需要主協程給機會。一般的作法用sleep,chan
阻塞,看起來讓人不爽,本文介紹sync.WaitGroup
型別結合defer
的特性,給出優雅的解決方案。
緣起
下面這段程式碼眾所周知不會打招呼
....
func main(){
go sayHi(){
fmt.Println("say hello......")
}()
fmt.Println("main groutine....")
}
等待
上述程式碼不會講hello
,在於主協程main
無等待(阻塞),子協程sayHi
沒有露臉的機會。
為了讓子協程sayHai上場,通常在主協程末了加這麼一句,讓它睡會兒
time.Sleep(1e9)
但想想不科學,如果子協程在它睡期間,沒能完成任務,超時了,子協程仍然打不了招呼。又或者子協程完成了,主協程還在睡,豈不是主執行緒不作為?睡的時間多久怎樣才合理...
通道
用通道可解決阻塞時間合理性質疑。
若啟用了多個子協程,可以這樣實現主協程等待子協程執行完畢並退出的:宣告一個和子協程數量一致的通道陣列,然後為每個子協程分配一個通道元素,在子協程執行完畢時向對應的通道傳送資料;然後在主協程中,依次讀取這些通道接收子協程傳送的資料,只有所有通道都接收到資料才會退出主協程。
程式碼看起來像這樣
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go add(1, i, chs[i])
}
for _, ch := range chs {
<- ch
}
感覺這樣的實現有點蹩腳,不夠優雅,於是 sync.waitGroup
入場了
WaitGroup 型別
sync.WaitGroup
型別是併發安全的,提供了以下三個方法:
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
Add
:WaitGroup 型別有一個計數器,預設值是0,通常通過個方法來標記需要等待的子協程數量Done
:當某個子協程執行完畢後,可以通過 Done 方法標記已完成,常用 defer 語句來呼叫Wait
阻塞當前協程,直到對應WaitGroup
型別例項的計數器值歸零
程式碼
至此,相信你已經明白了,可組合使用 sync.WaitGroup
型別提供的方法,來替代之前通道中等待子協程執行完畢。
優雅等待子協程執行完畢程式碼如下
package main
import (
"fmt"
"sync"
)
func add_num(a, b int, done func()) {
defer done()
c := a + b
fmt.Printf("%d + %d = %d\n", a, b, c)
}
func main() {
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go add_num(i, 1, wg.Done)
}
wg.Wait()
}
補充
其實runtime 執行時預設是隻開啟一個邏輯處理器,如果不想用通道,也不阻塞,可以考慮另開戰場,比如 像下面這樣
runtime.GOMAXPROCS(n) // 設定使用多核邏輯處理器,n大於1