极客时间--golang并发编程实战课--WaitGroup学习总结
什么是WaitGroup?
WaitGroup解决的就是并发-等待问题:现在有一个goroutineA在检查点等待一组goroutine全部完成,如果
在执行任务的这些goroutine还没全部完成,那么goroutine A就会阻塞在检查点,知道所有goroutine都完成后才能继续执行。
Golang的标准库的WaitGroup提供了三个方法:
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg &WaitGroup) Wait()
Add :用来设置waitgroup的计数值
Done:用来将waitgroup的计数值减1,其实就是调用了Add(-1)
Wait:调用这个方法的goroutine会一直阻塞,直到WaitGroup的计数器的计数值为0
代码示例:启动10个woker,分别对计数值+1,10个worker都完成后,我们希望打印出计数器的值
package main
import (
"fmt"
"sync"
"time"
)
//一个线程安全的计数器
type Counter struct {
mu sync.Mutex
count uint64
}
//对计数器的值➕1
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
//获取当前的计数器的值
func (c *Counter) Count() uint64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
//sleep+1,然后计数+1
func Worker(c *Counter, wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(time.Second)
c.Incr()
}
func main() {
var count Counter
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go Worker(&count, &wg)
}
wg.Wait()
fmt.Println(count.Count())
}
WaitGroup的实现
数据结构如下:
type WaitGroup struct {
noCopy noCopy//避免复制使用的一个技巧,可以告诉vet工具违反了复制使用的规则
state1 uint64
state2 uint32
}
Add方法的逻辑:主要操作的是state的计数部分,可以为计数值增加一个delta值,内部通过原子操作把这个值加到计数器上,需要注意的是,这个delta值可以为负数,Done方法内部其实就是通过Add(-1)实现的。
Wait方法的逻辑:不断检查delta值,如果其中的计数值==0,那么说明所有的任务已完成,调用者不必等待,直接返回,如果计数值大于0,说明此时还有任务没完成,那么调用者就变成了等待者,需要加入waiter队列,并且阻塞住自己。
使用总结:
保证所有的Add方法调用都在wait之前
不传递负数给Add方法,只通过Done来给计数值-1
不做多余的Done方法调用,保证Add的计数值和Done方法调用的数量是一致的
不遗漏Done方法的调用,否则会导致panic
附来自课程上的一幅插图: