如果你不关心并发操作的结果,或者有其他方式收集结果,那么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! ``` * * * * * 学识浅薄,错误在所难免。我是长风,欢迎来Golang中国的群(211938256)就本书提出修改意见。