sync.WaitGroup可以使得我们很优雅的等待协程的退出
写一个普通的例子。下面的例子中,say函数中的字符串不会输出,因为main函数也是一个协程,say函数相当于一个子协程,父协程运行完退出后,子协程也会退出,并不会等待子协程中的任务完成,而要想执行完子协程中的任务,最简单的方法是让父协程等待一段时间,子协程可以在这段时间内执行完任务。去掉下面的注释后,say函数就可以执行完内部的任务。
package main
import (
"fmt"
"time"
)
func say() {
fmt.Println("hello world")
}
func main() {
for i := 0; i < 10; i++ {
go say()
}
fmt.Println("I Love You")
// time.Sleep(time.Second) // 去掉注释
}
但是,我们并不知道子协程要执行多长时间,当执行I/O操作时,我们无法确保准确的时间,所以以上的方法不能用于正常的逻辑中。
此时我们可以使用sync.WaitGroup来控制,该对象主要维护一个计数器。该对象里面有三个重要的方法,(wg * WaitGroup) Add(delta int)
方法可以指定计数器数值,计数器的数值决定了子协程的个数,该函数应该在父协程中使用;(wg *WaitGroup) Done()
方法使得计数器 -1 ,该函数应该在子协程中使用;(wg *WaitGroup) Wait()
方法用于阻塞父协程,直到等待计数器减到0时,父协程才可以继续往下执行,该函数应该用于父协程。
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup // 定义全局的结构体变量
func say() {
defer wg.Done() // 计数器 -1
fmt.Println("hello world")
}
func main() {
wg.Add(5) // 计数器为5
for i := 0; i < 5; i++ {
go say()
}
fmt.Println("I Love You")
wg.Wait() // 阻塞父协程
fmt.Println("I Love You")
}
/* 结果
I Love You
hello world
hello world
hello world
hello world
hello world
I Love You
*/