等待过程中的未知性让人感到快乐又痛苦。
在之前创建好了一个协程之后,为了让这个协程有时间执行完成,我们在主协程里面睡了几秒,用的是time.Sleep,而这种情况是在可以知道协程执行完的时间才可以知道需要睡多长的时间。在go里面有一个sync的包,是用来做同步的或者说控制协程之间的安全问题。里面有个WaitGroup的结构体,从名字上来看它是用于等待的,Group表示一群,可知它是可以等待多个协程完成的。其结构体如下:
type WaitGroup struct {
noCopy noCopy
state atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count.
sema uint32
}
虽然作者没有在里面给出每个属性的含义,我们可以从它的注释来理解这个结构体是做什么的。
// A WaitGroup waits for a collection of goroutines to finish. // The main goroutine calls Add to set the number of // goroutines to wait for. Then each of the goroutines // runs and calls Done when finished. At the same time, // Wait can be used to block until all goroutines have finished.
从上面的注释来看,它是可以用来等待多个协程的任务完成的,使用方法是在main 协程调用Add方法设置需要等待的协程的个数,然后在每个需要等待的协程里面调用Done方法表示这个协程的任务已经完成了,hai'you 一个Wait方法用于阻塞/等待前面创建的协程完成。
现在我们开始来使用它了
package main
import (
"fmt"
"sync"
)
func main() {
//创建wg
wg := sync.WaitGroup{}
//需要等待2个协程
wg.Add(2)
go printOne(&wg)
go printTwo(&wg)
//主协程等待前面的2个协程执行完任务
wg.Wait()
fmt.Println("main end")
}
//任务1
func printOne(wg *sync.WaitGroup) {
fmt.Println("one")
wg.Done()
}
//任务2
func printTwo(wg *sync.WaitGroup) {
fmt.Println("two")
wg.Done()
}
执行程序后我们发现,main end 是最后才打印的。需要注意的是我们在把wg作为一个参数传给函数的时候,使用了wg的地址的,因为调用Done方法涉及到修改state属性的值,并且这个state会被多个协程来修改的。为了确保修改的安全问题 state的类型也是来自atomic原子包的Uint64。
总结 使用WaitGroup解决了使用time.Sleep的缺点,不需要知道需要等多长的时间。而这个使用场景在生活里面也是很常见的,当团队协作的时候对于一些高级别的任务需要基础性的工作来做支撑的时候,往往需要先把基础工作做好,上层的建设才可以稳步开始。