Golang sync.WaitGroup 简介与用法

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 程计数器减 1
func (wg *WaitGroup) Done()

//阻塞等待所有 Go 程结束(等待 Go 程计数器变为 0)
func (wg *WaitGroup) Wait()

WaitGroup 有三个方法,其中 Done() 调用了 Add(-1)。标准用法:
(1)启动 Go 程时调用 Add();
(2)在 Go 程结束时调用 Done();
(3)最后调用 Wait()。

2.使用示例

package main

import (
    "fmt"
    "sync"    
    "time"
)

var wg sync.WaitGroup
	
func foo1() {
    defer wg.Done()
    fmt.Println("entry foo1")
    time.Sleep(2 * time.Second)
    fmt.Println("exit foo1")
}


func foo2() {
    defer wg.Done()
    fmt.Println("entry foo2")
    time.Sleep(4 * time.Second)
    fmt.Println("exit foo2")
}

func main() {
    fmt.Println("entry main")
	
    wg.Add(2)
    go foo1()
    go foo2()

    fmt.Println("wg.Wait()")
    wg.Wait()
    
    fmt.Println("exit main")
}

编译运行输出:

entry main
wg.Wait()
entry foo2
entry foo1
exit foo1
exit foo2
exit main

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 main
panic: sync: negative WaitGroup counter

goroutine 1 [running]:
sync.(*WaitGroup).Add(0xc4200140d0, 0xffffffffffffffff)
	/usr/lib/golang/src/sync/waitgroup.go:75 +0x134
sync.(*WaitGroup).Done(0xc4200140d0)
	/usr/lib/golang/src/sync/waitgroup.go:100 +0x34
main.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("entry foo2")
    time.Sleep(4 * time.Second)
    fmt.Println("exit foo2")
}

编译运行输出:

entry main
wg.Wait()
entry foo2
entry foo1
exit foo1
exit foo2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x54aa7c)
	/usr/lib/golang/src/runtime/sema.go:56 +0x39
sync.(*WaitGroup).Wait(0x54aa70)
	/usr/lib/golang/src/sync/waitgroup.go:131 +0x72
main.main()
	/data/goTest/src/waitgroup/main.go:33 +0x10e

这个错误表明,在最后一个活动线程 foo2 退出的时候,Go 检测到当前没有还在运行的 Go 程,但主 Go 程仍在等待,发生了死锁现象,于是引发 panic,这是 Go 的一种自我保护机制。


参考文献

[1] Golang
[2] golang语言异步通信之WaitGroup

你可能感兴趣的:(Go,基础)