Golang 基础 Part 3: WaitGroup 等待组
文章讨论了 WaitGroup
在 Golang 中的基础知识、怎样使用 WaitGroup
以及使用 WaitGroup
需要注意的地方。
很多情况下,我们正需要知道 goroutine 是否完成。这需要借助 sync
包的 WaitGroup
来实现。WaitGroup
是 sync
包中的一个 struct
类型,用来收集需要等待执行完成的 goroutine。下面是它的定义:
1type WaitGroup struct {
2 // Has unexported fields.
3}
4 // A WaitGroup waits for a collection of goroutines to finish. The main
5 // goroutine calls Add to set the number of goroutines to wait for. Then each
6 // of the goroutines runs and calls Done when finished. At the same time, Wait
7 // can be used to block until all goroutines have finished.
8
9 // A WaitGroup must not be copied after first use.
10
11
12func (wg *WaitGroup) Add(delta int)
13func (wg *WaitGroup) Done()
14func (wg *WaitGroup) Wait()
它有3个方法:
Add()
:每次激活想要被等待完成的 goroutine 之前,先调用Add()
,用来设置或添加要等待完成的 goroutine 数量例如Add(2)
或者两次调用Add(1)
都会设置等待计数器的值为2,表示要等待2个 goroutine 完成Done()
:每次需要等待的 goroutine 在真正完成之前,应该调用该方法来人为表示 goroutine 完成了,该方法会对等待计数器减1Wait()
:在等待计数器减为0之前,Wait()
会一直阻塞当前的 goroutine
1package main
2
3import (
4 "fmt"
5 "sync"
6 "time"
7)
8
9func process(i int, wg *sync.WaitGroup) {
10 fmt.Println("started Goroutine ", i)
11 time.Sleep(2 * time.Second)
12 fmt.Printf("Goroutine %d ended\n", i)
13 wg.Done()
14}
15
16func main() {
17 no := 3
18 var wg sync.WaitGroup
19 for i := 0; i < no; i++ {
20 wg.Add(1)
21 go process(i, &wg)
22 }
23 wg.Wait()
24 fmt.Println("All go routines finished executing")
25}
上面激活了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 将会永久阻塞而导致产生死锁。