WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。 
但在使用时,也有一些问题需要注意,请看本文的详细分解。 
另外,WaitGroup的使用场景十分有限,为什么呢?具体原因,请看本文的总结部分的分析。

主要函数

func (*WaitGroup) Add

func (wg *WaitGroup) Add(delta int)

Add方法向内部计数加上delta,delta可以是负数;如果内部计数器变为0,Wait方法阻塞等待的所有协程都会释放,如果计数器小于0,则调用panic。注意Add加上正数的调用应在Wait之前,否则Wait可能只会等待很少的协程。一般来说本方法应在创建新的协程或者其他应等待的事件之前调用。

func (wg *WaitGroup) Done()

func (wg *WaitGroup) Done()

Done方法减少WaitGroup计数器的值,应在协程的最后执行。

func (wg *WaitGroup) Wait()

func (wg *WaitGroup) Wait()

Wait方法阻塞直到WaitGroup计数器减为0。

编程实战

等待某个协程结束

func main() {
var wg sync.WaitGroup

wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("1 goroutine sleep ...")
time.Sleep(2e9)
fmt.Println("1 goroutine exit ...")
}()

wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("2 goroutine sleep ...")
time.Sleep(4e9)
fmt.Println("2 goroutine exit ...")
}()

fmt.Println("waiting for all goroutine ")
wg.Wait()
fmt.Println("All goroutines finished!")
}

小结

要注意Add和Done函数一定要配对,否则可能发生死锁,所报的错误信息如下:

fatal error: all goroutines are asleep - deadlock!
等待协程组结束

func main() {
sayHello := func(wg *sync.WaitGroup, id int) {
defer wg.Done()
fmt.Printf("%v goroutine start ...\n", id)
time.Sleep(2)
fmt.Printf("%v goroutine exit ...\n", id)
}

var wg sync.WaitGroup
const N = 5
wg.Add(N)
for i := 0; i < N; i++ {
go sayHello(&wg, i)
}

fmt.Println("waiting for all goroutine ")
wg.Wait()
fmt.Println("All goroutines finished!")
}

运行以上程序,输出如下:

waiting for all goroutine 
1 goroutine start ...
5 goroutine start ...
5 goroutine exit ...
4 goroutine start ...
4 goroutine exit ...
3 goroutine start ...
1 goroutine exit ...
0 goroutine start ...
3 goroutine exit ...
0 goroutine exit ...
2 goroutine start ...
2 goroutine exit ...
All goroutines finished!

无论运行多少次,都能保证All goroutines finished!这一句在最后一行输出,这说明,Wait()函数等了所有协程都结束自己才返回。

小结

以上程序通过WaitGroup提供的三个同步接口,实现了等待一个协程组完成的同步操作。在实现时要注意: 
* Add的参数N必须和创建的goroutine的数量相等,否则会报出死锁的错误信息 
* 另外,sayHello()函数中要传递WaitGroup的指针呢?在该结构的实现源码中已经有讲解:

A WaitGroup must not be copied after first use.
就是说,该结构定义后就不能被复制,所以这里要使用指针。
总结

通过WaitGroup提供的三个函数:Add,Done,Wait,可以轻松实现等待某个协程或协程组完成的同步操作。但在使用时要注意:

Add的数量和Done的调用数量必须相等。
另外,就是WaitGroup结构一旦定义就不能复制的原因。
WaitGroup在需要等待多个任务结束再返回的业务来说还是很有用的,但现实中用的更多的可能是,先等待一个协程组,若所有协程组都正确完成,则一直等到所有协程组结束;若其中有一个协程发生错误,则告诉协程组的其他协程,全部停止运行(本次任务失败)以免浪费系统资源。 
该场景WaitGroup是无法实现的,那么该场景该如何实现呢,就需要用到通知机制,其实也可以用channel来实现,具体的解决办法,请看后续的文章。 
这样说来,WaitGroup的使用场景是有限的。