文章目录
sync.WaitGroup
使用方式
先来看一个例子,开启100个协程执行加法操作,开启10个协程读取加法的和。
var s int32
func main() {
for i := 0; i < 100; i++ {
go func() {
s += 10
time.Sleep(time.Second * 2)
}()
}
for i := 0; i < 10; i++ {
go func(index int) {
fmt.Println("index=", index, "sum=", s)
}(i)
}
fmt.Println("sum=", s)
time.Sleep(time.Second * 5)
}
- 在以上实示例中,有这样一段代码"time.Sleep(time.Second*5)"是为了防止主函数main提前返回,其余goroutine来不及执行。但是有这样一个问题,我们无法确定100个执行加法的协程和执行10个读取的协程什么时候会执行完成,如果设置的等待时间太长会导致主协程等待的时间太长,如果设置的等待的时间太短,会导致任务还没有执行完成就提前退出。
- 为了解决上述的问题,引入了waitgroup。主要的作用就是用来等待其余协程全部执行完成之后再推出。
为什么主协程执行完后就会退出,不会等待子协程
可以参考视频:“Hello Goroutine的执行过程”
上述的例子用waitgroup进行改写
func main() {
wg := sync.WaitGroup{
}
wg.Add(110)
//100个goroutine用来执行写操作
for i := 0; i < 100; i++ {
go func() {
defer wg.Done()
s += 10
time.Sleep(time.Second * 2)
}()
}
//10个goroutine执行读操作
for i := 0; i < 10; i++ {
go func(index int) {
defer wg.Done()
fmt.Println("index=", index, "sum=", s)
}(i)
}
fmt.Println("sum=", s)
wg.Wait()
}
-
声明一个sync.WaitGroup,然后通过Add方法设置计数器的值,需要等待多少数量的协程就设置成多少。
-
在每个协程执行完毕时调用Done方法,让计数器减1,告诉Wait该协程已经执行完毕。
-
最后调用Wait方法一直等待,直到计数器的值为0,也即是所有的协程都执行完毕。此时主协程可以退出。
sync.WaitGroup适合协调多个协程共同做一件事的场景,比如下载一个文件,假设使用10个协程,每个协程下载文件的1/10大小,只有10个协程都下载好了整个文件才算下载好。
底层原理
WaitGroup是一个结构体,先来看一下它的定义
/*
WaitGroup可以等待一些数量的协程完成相关的逻辑操作
主协程调用"Add"方法增加需要等待的正在运行的协程的数量;
每一个协程执行完自己的逻辑之后调用"Done"方法将需要等待的协程数量减少。与此同时"Wait"方法会阻塞直到需要等待的协程全部退出。
waitGroup在第一次使用之后禁止复制
在Wait阻塞之前Done的调用是并发安全的
*/
type WaitGroup struct {
//type noCopy struct{}
noCopy noCopy
/*
高32bit充当计数器的功能,记录需要追踪的协程数量。
低32bit记录的是,因为wait操作而被阻塞的协程数量。
*/
state atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count.
sema uint32//用来同步Add中的读操作和Wait中的写操作
}
看"Add\Done\Wait"方法之前先看一下,有关信号量的两个操作
/*
Semacquire等待直到s中存储的数值大于0,然后自动减少它
它是一个简单的睡眠原语
*/
func runtime_Semacquire(s *uint32)
/*
Semrelease自动增加s中存储的数值,并通知一个等待该信号量的协程;
是一个简单的唤醒原语
handoff为true,直接把count传递给第一个waiter
skipframes是跟踪过程中要忽略的帧数,从runtime_Semrelease调用者开始计数。
*/
func runtime_Semrelease(s *uint32, handoff bool, skipframes int)
Add
先来看一看"Add"方法
/*
Add方法向WaitGroup的计数器添加delta,有可能变为负值;
如果计数器变为0,所有被Wait阻塞的协程都将被释放
如果计数器变为负数,则会产生panic
*/
func (wg *WaitGroup) Add(delta