如果你不关心并发操作的结果,或者有其他方式收集结果,那么WaitGroup是等待一组并发操作完成的好方法。如果这两个条件都不成立,我建议你改用channel和select语句。WaitGroup非常有用,我先介绍它,以便在后续章节中使用它。以下是使用WaitGroup等待goroutine完成的基本示例:

var wg sync.WaitGroup

wg.Add(1) //1
go func() {
    defer wg.Done() //2
    fmt.Println("1st goroutine sleeping...")
    time.Sleep(1)
}()

wg.Add(1) //1
go func() {
    defer wg.Done() //2
    fmt.Println("2nd goroutine sleeping...")
    time.Sleep(2)
}()

wg.Wait() //3
fmt.Println("All goroutines complete.")
  1. 这里我们调用Add并传入参数1来表示一个goroutine正在开始。
  2. 在这里我们使用defer关键字来调用Done,以确保在退出goroutine的闭包之前,向WaitGroup表明了我们已经退出。
  3. 在这里,我们调用Wait,这将main goroutine,直到所有的goroutine都表明它们已经退出。

这会输出:

2nd goroutine sleeping... 
1st goroutine sleeping... 
All goroutines complete.

你可以把WaitGroup视作一个安全的并发计数器:调用Add增加计数,调用Done减少计数。调用Wait会阻塞并等待至计数器归零。

请注意,Add的调用是在goroutines之外完成的。 如果没有这样做,我们会引入一个数据竞争条件,因为我们没有对goroutine做任何调度顺序上的保证; 我们可能在任何一个goroutines开始前触发Wait调用。 如果Add的调用被放置在goroutines的闭包中,对Wait的调用可能完全没有阻塞地返回,因为Add没有被执行。

通常情况下,尽可能与要跟踪的goroutine就近且成对的调用Add,但有时候会一次性调用Add来跟踪一组goroutine。我通常会做这样的循环:

hello := func(wg *sync.WaitGroup, id int) {
    defer wg.Done()
    fmt.Printf("Hello from %v!\n", id)
}

const numGreeters = 5
var wg sync.WaitGroup
wg.Add(numGreeters)
for i := 0; i < numGreeters; i++ {
    go hello(&wg, i+1)
}
wg.Wait()

这会输出:

Hello from 5!
Hello from 4!
Hello from 3!
Hello from 2!
Hello from 1!
最后编辑: kuteng  文档更新时间: 2021-01-02 17:30   作者:kuteng