go 语言 sync.Wait的使用
如何让主 goroutine 在所有其他 goroutine 都运行完后,再退出
sleep
通过 sleep 让主 goroutine 在所有子goroutine运行结束后,再退出
func usingSleep() {
for i := 0; i < 10; i++ {
go fmt.Println(i)
}
time.Sleep(time.Second)
}
缺点:无法判断 for 循环到底需要多长时间,导致估计sleep的时间要么不够,要么太长
缓冲通道
通过缓冲通道让主 goroutine 在所有子goroutine运行结束后,再退出
func usingChannel() {
c := make(chan struct{}, 10)
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println(i)
c <- struct{}{}
}(i)
}
for i := 0; i < 10; i++ {
<-c
}
}
缺点:
- channel应该被用在goroutine间的通信
- 如果子goroutine太多,使用通道也会消耗很多资源
sync.WaitGroup
使用 sync.WaitGroup()控制主 goroutine 在所有子goroutine运行结束后,再退出
func waitGroup() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println(i)
}(i)
}
wg.Wait() // 一直阻塞主goroutine,直到wg为零
// output
// 乱序输出,并不是按1,2,3,4...
// 2
// 4
// 7
// 3
// 1
// 0
// 9
// 5
// 8
// 6
}
Wait() 方法的功能是,阻塞当前的 goroutine,直到其所属值中的计数器归零
不要把增加其计数器值的操作和调用其Wait方法的代码,放在不同的 goroutine 中执行,如果同时启用的两个 goroutine ,分别调用这两个方法(add 和 wait),那么就有可能会让这里的Add方法抛出一个 panic。最好用“先统一Add,再并发Done,最后Wait”这种标准方式,来使用WaitGroup值
计数器不能为负值,发生为负的情况是:不适当地调用Done方法和Add方法都会如此。
Done()是Add(-1)的别名
计数不为0, 阻塞Wait()的运行
WaitGroup对象不是一个引用类型
在通过函数传值的时候需要使用地址,不然会出现 fatal error: all goroutines are asleep - deadlock!
func passWGByPointer() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go wgDone(i, &wg)
}
wg.Wait()
}
func wgDone(i int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println(i)
}