之前写的协程有关的例子,为了保证所有协程都执行完毕后退出,我们都使用了 ​​time.Sleep(time.Millisecond)​​ 使主协程休眠,但在实际开发中我们并不能保证要休眠多久才能执行完毕,用多了主程序就阻塞了,用少了又会导致子协程的任务可能没法完成。下面我们介绍处理这种情况的方式。


Go 语言系列26:WaitGroup_i++

使用信道

Go 语言系列26:WaitGroup_其它_02


信道可以实现多个协程间的通信,于是乎我们可以定义一个信道,在任务执行完成后,往信道中写入 ​​true​​​ ,然后在主协程中获取到 ​​true​​ ,就可以认为子协程已经执行完毕。

package main

import "fmt"

func main() {
isDone := make(chan bool)
go func() {
for i := 0; i < 5; i++{
fmt.Println(i)
}
isDone <- true
}()
<- isDone
}

运行上面的程序,主协程就会等待创建的协程执行完毕后退出,运行后输出如下:

0
1
2
3
4

Go 语言系列26:WaitGroup_i++

使用 WaitGroup

Go 语言系列26:WaitGroup_其它_02


使用上面的信道方法,虽然可行,但在你程序中使用很多协程的话,你的代码就会看起来很复杂,这里就要介绍一种更好的方法,那就是使用 ​​sync​​ 包中提供的 WaitGroup 类型。​​WaitGroup​​​ 用于等待一批 Go 协程执行结束。程序控制会一直阻塞,直到这些协程全部执行完毕。当然 ​​WaitGroup​​ 也可以用于实现工作池。

​WaitGroup​​ 实例化后就能使用:

var name sync.WaitGroup

​WaitGroup​​ 有几个方法:

​Add​​​:初始值为 ​​0​​​ ,这里直接传入子协程的数量,你传入的值会往计数器上加。​​Done​​​:当某个子协程完成后,可调用此方法,会从计数器上减一,即子协程的数量减一,通常使用 ​​defer​​​ 来调用。​​Wait​​:阻塞当前协程,直到实例里的计数器归零。

package main

import (
"fmt"
"sync"
)

func task(taskNum int, waitGroup *sync.WaitGroup) {
// 延迟调用 执行完子协程计数器减一
defer waitGroup.Done()
// 输出任务号
for i := 0; i < 3; i++ {
fmt.Printf("task %d: %d\n", taskNum, i)
}
}

func main() {
// 实例化 sync.WaitGroup
var waitGroup sync.WaitGroup
// 传入子协程的数量
waitGroup.Add(3)
// 开启一个子协程 协程 1 以及 实例 waitGroup
go task(1, &waitGroup)
// 开启一个子协程 协程 2 以及 实例 waitGroup
go task(2, &waitGroup)
// 开启一个子协程 协程 3 以及 实例 waitGroup
go task(3, &waitGroup)
// 实例 waitGroup 阻塞当前协程 等待所有子协程执行完
waitGroup.Wait()
}

运行上面的程序一种输出如下:

task 3: 0
task 3: 1
task 3: 2
task 1: 0
task 1: 1
task 1: 2
task 2: 0
task 2: 1
task 2: 2

参考文献:

[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.