1. 简介
golang为并发执行提供了WaitGroup工具,方便goroutine执行的控制,但是缺少了error的传递和一个goroutine出错,取消其他goroutine执行能力。通过对WaitGroup的进一步封装,就变成了errgroup,可以很好的解决上述问题
2. 核心结构体
type Group struct {
// context的cancel方法
cancel func()
// 控制多个g的执行
wg sync.WaitGroup
// 对同时执行的g的数量控制
sem chan token
// Once 保证逻辑只执行一次,只接收产生的第一个error
errOnce sync.Once
// 第一个产生的error
err error
}
Group结构体的核心是WaitGroup, 配合其他字段来完成功能
3. 初始化
func WithContext(ctx context.Context) (*Group, context.Context) {
// 创建一个可取消的context
ctx, cancel := context.WithCancel(ctx)
// 初始化结构体 直接返回
return &Group{cancel: cancel}, ctx
}
创建一个可取消的context, 复制给Group直接返回
4. 执行逻辑
func (g *Group) Go(f func() error) {
// 使用带缓冲的channel,进行g控制,是阻塞的
if g.sem != nil {
g.sem <- token{}
}
// 使用wg进行控制
g.wg.Add(1)
go func() {
// defer 保证 g.done 最终执行
defer g.done()
// 如果执行方法 返回了err,记录err,执行context的cacenl方法,由其他g根据context的done()自行判断是否需要退出
if err := f(); err != nil {
g.errOnce.Do(func() {
g.err = err
if g.cancel != nil {
g.cancel()
}
})
}
}()
}
Go方法会启动一个协程,使用waitgroup进行控制
5. wait方法
func (g *Group) Wait() error {
g.wg.Wait()
if g.cancel != nil {
g.cancel()
}
return g.err
}
使用waitgroup进行等待, 并返回error
6. 对g数量的控制
6.1 SetLimit设置限制数量
func (g *Group) SetLimit(n int) {
if n < 0 {
g.sem = nil
return
}
if len(g.sem) != 0 {
panic(fmt.Errorf("errgroup: modify limit while %v goroutines in the group are still active", len(g.sem)))
}
// 设置chan的缓冲长度
g.sem = make(chan token, n)
}
设置chan的缓冲长度来控制g数量
6.2 Go执行时进行控制
func (g *Group) Go(f func() error) {
// 设置过则进行判断
if g.sem != nil {
// 如果chan满了,则进行阻塞限制下面新g
// 未满时,不会阻塞执行新g
g.sem <- token{}
}
g.wg.Add(1)
go func() {}
}
6.3 done方法,标志g执行结束
func (g *Group) done() {
if g.sem != nil {
// 取出chan
<-g.sem
}
g.wg.Done()
}
1. g的数量限制通过带缓冲的chan实现的,chan未满时,不阻塞执行新g, 满了时,会阻塞,等待有新g。
2. goroutine执行结束后调用done方法,取出chan,下次再执行Go方法时,就不会阻塞