Golang sync.WaitGroup 简介与用法
发布日期:2021-06-29 19:23:48 浏览次数:2 分类:技术文章

本文共 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Golang sync.Pool 简介与用法
下一篇:Golang sync.Map 简介与用法

发表评论

最新留言

很好
[***.229.124.182]2024年04月13日 01时45分59秒