Go 学习笔记(二十三)WaitGroup 用法说明

本文原创地址:博客园骏马金龙Go 基础系列:WaitGroup 用法说明

正常情况下,新激活的 goroutine 的结束过程是不可控制的,唯一可以保证终止 goroutine 的行为是 main goroutine 的终止。也就是说,我们并不知道哪个 goroutine 什么时候结束。

但很多情况下,我们正需要知道 goroutine 是否完成。这需要借助 sync 包的 WaitGroup 来实现。

WatiGroup 是 sync 包中的一个 struct 类型,用来收集需要等待执行完成的 goroutine。下面是它的定义:

type WaitGroup struct {
		// Has unexported fields.
}
	A WaitGroup waits for a collection of goroutines to finish. The main
	goroutine calls Add to set the number of goroutines to wait for. Then each
	of the goroutines runs and calls Done when finished. At the same time, Wait
	can be used to block until all goroutines have finished.

	A WaitGroup must not be copied after first use.


func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()

它有 3 个方法:

  • Add():每次激活想要被等待完成的 goroutine 之前,先调用 Add(),用来设置或添加要等待完成的 goroutine 数量
    • 例如 Add(2) 或者两次调用 Add(1) 都会设置等待计数器的值为 2,表示要等待 2 个 goroutine 完成
  • Done():每次需要等待的 goroutine 在真正完成之前,应该调用该方法来人为表示 goroutine 完成了,该方法会对等待计数器减 1
  • Wait():在等待计数器减为 0 之前,Wait() 会一直阻塞当前的 goroutine

也就是说,Add()用来增加要等待的 goroutine 的数量,Done() 用来表示 goroutine 已经完成了,减少一次计数器,Wait() 用来等待所有需要等待的 goroutine 完成。

下面是一个示例,通过示例很容易理解。

package main

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

func process(i int, wg *sync.WaitGroup) {  
	fmt.Println("started Goroutine ", i)
	time.Sleep(2 * time.Second)
	fmt.Printf("Goroutine %d ended\n", i)
	wg.Done()
}

func main() {  
	no := 3
	var wg sync.WaitGroup
	for i := 0; i < no; i++ {
		wg.Add(1)
		go process(i, &wg)
	}
	wg.Wait()
	fmt.Println("All go routines finished executing")
}

上面激活了 3 个 goroutine,每次激活 goroutine 之前,都先调用 Add()方法增加一个需要等待的 goroutine 计数。每个 goroutine 都运行 process()函数,这个函数在执行完成时需要调用 Done()方法来表示 goroutine 的结束。激活 3 个 goroutine 后,main goroutine 会执行到 Wait(),由于每个激活的 goroutine 运行的 process()都需要睡眠 2 秒,所以 main goroutine 在 Wait()这里会阻塞一段时间 (大约 2 秒),当所有 goroutine 都完成后,等待计数器减为 0,Wait()将不再阻塞,于是 main goroutine 得以执行后面的 Println()。

还有一点需要特别注意的是 process() 中使用指针类型的*sync.WaitGroup作为参数 ,这里不能使用值类型的sync.WaitGroup作为参数,因为这意味着每个 goroutine 都拷贝一份 wg,每个 goroutine 都使用自己的 wg。这显然是不合理的, 这 3 个 goroutine 应该共享一个 wg ,才能知道这 3 个 goroutine 都完成了。实际上,如果使用值类型的参数,main goroutine 将会永久阻塞而导致产生死锁。
上一篇 Go 学习笔记(二十二)读取标准输入
Go 学习笔记(目录)
下一篇 Go 学习笔记(二十四)channel 入门