如果你不关心并发操作的结果,或者有其他方式收集结果,那么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运行时
- 任务调度