如果你不關心并發操作的結果,或者有其他方式收集結果,那么WaitGroup是等待一組并發操作完成的好方法。如果這兩個條件都不成立,我建議你改用channel和select語句。WaitGroup非常有用,我先介紹它,以便在后續章節中使用它。以下是使用WaitGroup等待goroutine完成的基本示例:
```
var wg sync.WaitGroup
wg.Add(1) //1
go func() {
defer wg.Done() //2
fmt.Println("1st goroutine sleeping...")
time.Sleep(1)
}()
wg.Add(1) //1
go func() {
defer wg.Done() //2
fmt.Println("2nd goroutine sleeping...")
time.Sleep(2)
}()
wg.Wait() //3
fmt.Println("All goroutines complete.")
```
1. 這里我們調用Add并傳入參數1來表示一個goroutine正在開始。
2. 在這里我們使用defer關鍵字來調用Done,以確保在退出goroutine的閉包之前,向WaitGroup表明了我們已經退出。
3. 在這里,我們調用Wait,這將main goroutine,直到所有的goroutine都表明它們已經退出。
這會輸出:
```
2nd goroutine sleeping...
1st goroutine sleeping...
All goroutines complete.
```
你可以把WaitGroup視作一個安全的并發計數器:調用Add增加計數,調用Done減少計數。調用Wait會阻塞并等待至計數器歸零。
請注意,Add的調用是在goroutines之外完成的。 如果沒有這樣做,我們會引入一個數據競爭條件,因為我們沒有對goroutine做任何調度順序上的保證; 我們可能在任何一個goroutines開始前觸發Wait調用。 如果Add的調用被放置在goroutines的閉包中,對Wait的調用可能完全沒有阻塞地返回,因為Add沒有被執行。
通常情況下,盡可能與要跟蹤的goroutine就近且成對的調用Add,但有時候會一次性調用Add來跟蹤一組goroutine。我通常會做這樣的循環:
```
hello := func(wg *sync.WaitGroup, id int) {
defer wg.Done()
fmt.Printf("Hello from %v!\n", id)
}
const numGreeters = 5
var wg sync.WaitGroup
wg.Add(numGreeters)
for i := 0; i < numGreeters; i++ {
go hello(&wg, i+1)
}
wg.Wait()
```
這會輸出:
```
Hello from 5!
Hello from 4!
Hello from 3!
Hello from 2!
Hello from 1!
```
* * * * *
學識淺薄,錯誤在所難免。我是長風,歡迎來Golang中國的群(211938256)就本書提出修改意見。
- 前序
- 誰適合讀這本書
- 章節導讀
- 在線資源
- 第一章 并發編程介紹
- 摩爾定律,可伸縮網絡和我們所處的困境
- 為什么并發編程如此困難
- 數據競爭
- 原子性
- 內存訪問同步
- 死鎖,活鎖和鎖的饑餓問題
- 死鎖
- 活鎖
- 饑餓
- 并發安全性
- 優雅的面對復雜性
- 第二章 代碼建模:序列化交互處理
- 并發與并行
- 什么是CSP
- CSP在Go中的衍生物
- Go的并發哲學
- 第三章 Go的并發構建模塊
- Goroutines
- sync包
- WaitGroup
- Mutex和RWMutex
- Cond
- Once
- Pool
- Channels
- select語句
- GOMAXPROCS
- 結論
- 第四章 Go的并發編程范式
- 訪問范圍約束
- fo-select循環
- 防止Goroutine泄漏
- or-channel
- 錯誤處理
- 管道
- 構建管道的最佳實踐
- 便利的生成器
- 扇入扇出
- or-done-channel
- tee-channel
- bridge-channel
- 隊列
- context包
- 小結
- 第五章 可伸縮并發設計
- 錯誤傳遞
- 超時和取消
- 心跳
- 請求并發復制處理
- 速率限制
- Goroutines異常行為修復
- 本章小結
- 第六章 Goroutines和Go運行時
- 任務調度