本文共 2739 字,大约阅读时间需要 9 分钟。
1.简介
sync.WaitGroup 用于阻塞等待一组 Go 程的结束。主 Go 程调用 Add() 来设置等待的 Go 程数,然后该组中的每个 Go 程都需要在运行结束时调用 Done(), 递减 WaitGroup 的 Go 程计数器 counter。当 counter 变为 0 时,主 Go 程被唤醒继续执行。
type WaitGroup struct { // contains filtered or unexported fields}// 设置需要等待的 Go 程数量func (wg *WaitGroup) Add(delta int)// Go 程计数器减 1func (wg *WaitGroup) Done()// 阻塞等待所有 Go 程结束(等待 Go 程计数器变为 0)func (wg *WaitGroup) Wait()
WaitGroup 有三个方法,其中 Done() 调用了 Add(-1)。标准用法:
(1)启动 Go 程时调用 Add(); (2)在 Go 程结束时调用 Done(); (3)最后调用 Wait()。2.使用示例
package mainimport ( "fmt" "sync" "time") func foo1() { defer wg.Done() fmt.Println("exit foo1")}func foo2() { defer wg.Done() fmt.Println("exit foo2")}func main() { fmt.Println("entry main") var wg sync.WaitGroup wg.Add(2) go foo1() go foo2() fmt.Println("wg.Wait()") wg.Wait() fmt.Println("exit main")}
编译运行输出:
entry mainwg.Wait()exit foo2exit foo1exit main
注意: 多次执行输出结果中 “exit foo2” 和 “exit foo1” 的先后顺序是不定的,因为多协程并发执行的顺序是随机的。
3.错误示例
如果使用过程中通过 Add() 添加的 Go 程数与调用 Done() 的次数不符,即 sync.WaitGroup 的 Go 程计数器等所有子 Go 程结束后不为 0,则会引发 panic。
3.1 Done() 过多
func main() { fmt.Println("entry main") var wg sync.WaitGroup wg.Done() fmt.Println("wg.Wait()") wg.Wait() fmt.Println("exit main")}
编译运行输出:
entry mainpanic: sync: negative WaitGroup countergoroutine 1 [running]:sync.(*WaitGroup).Add(0xc4200140d0, 0xffffffffffffffff) /usr/lib/golang/src/sync/waitgroup.go:75 +0x134sync.(*WaitGroup).Done(0xc4200140d0) /usr/lib/golang/src/sync/waitgroup.go:100 +0x34main.main() /data/goTest/src/waitgroup/main.go:34 +0x8e
可见,当 Go 程计数器变为负数时,将引发 panic。
3.2 Done() 过少
注释掉 foo2() 中的 defer wg.Done()
,使得 Go 程结束时不减少 sync.WaitGroup 的 Go 程计数器。
func foo2() { //defer wg.Done() fmt.Println("exit foo2")}
编译运行输出:
entry mainwg.Wait()exit foo1exit foo2fatal error: all goroutines are asleep - deadlock!goroutine 1 [semacquire]:sync.runtime_Semacquire(0x54aa7c) /usr/lib/golang/src/runtime/sema.go:56 +0x39sync.(*WaitGroup).Wait(0x54aa70) /usr/lib/golang/src/sync/waitgroup.go:131 +0x72main.main() /data/goTest/src/waitgroup/main.go:33 +0x10e
这个错误表明,在最后一个活动线程 foo2 退出的时候,Go 检测到当前没有还在运行的 Go 程,但主 Go 程仍在等待,发生了死锁现象,于是引发 panic,这是 Go 的一种自我保护机制。
4.errgroup
鉴于使用 sync.WaitGroup 容易出错,Go 官方包 errgroup 进一步对 sync.WaitGroup 进行了封装,不再需要 Add 和 Done,用起来更加简单方便。详见 。
package mainimport ( "fmt" "sync" "golang.org/x/sync/errgroup")func foo1() { fmt.Println("exit foo1")}func foo2() { fmt.Println("exit foo2")}func main() { fmt.Println("entry main") var g errgroup.Group g.Go(func() error { foo1() return nil }) g.Go(func() error { foo2() return nil }) fmt.Println("g.Wait()") g.Wait() fmt.Println("exit main")}
运行输出:
entry mainwg.Wait()exit foo2exit foo1exit main
参考文献
[1]
[2]转载地址:https://dablelv.blog.csdn.net/article/details/95641101 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!