errgroup 分析
errgroup 在 WaitGroup
的基础上实现子协程错误传递, 同时使用 context 控制协程的生命周期。
使用
errgroup 的使用非常简单
package main
import (
"context"
"fmt"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
group, _ := errgroup.WithContext(context.Background())
for i := 0; i < 5; i++ {
index := i
group.Go(func() error {
fmt.Printf("start to execute the %d gorouting\n", index)
time.Sleep(time.Duration(index) * time.Second)
if index%2 == 0 {
return fmt.Errorf("something has failed on grouting:%d", index)
}
fmt.Printf("gorouting:%d end\n", index)
return nil
})
}
if err := group.Wait(); err != nil {
fmt.Println(err)
}
}
输出
输出结果如下:
start to execute the 0 gorouting
start to execute the 3 gorouting
start to execute the 2 gorouting
start to execute the 1 gorouting
start to execute the 4 gorouting
gorouting:1 end
gorouting:3 end
something has failed on grouting:0
代码分析
不管是否有协程执行失败, wait()都要等待所有协程执行完成
使用方法与 WaitGroup
类似。只是封装了 WaitGroup
的 Add()
和 Wait()
方法。
- 首先传递 context 初始化 errgroup 对象
- 每一个 group.Go() 都会新启一个协程,
Go()
函数接受一个 func() error 函数类型 - 使用
Wait()
方法阻塞主协程,直到所有子协程执行完成
分析
errGroup 的结构如下:
type Group struct {
cancel func() //context cancel()
wg sync.WaitGroup
errOnce sync.Once //只会传递第一个出现错的协程的 error
err error //传递子协程错误
}
withContext
func WithContext(ctx context.Context) (*Group, context.Context) {
ctx, cancel := context.WithCancel(ctx)
return &Group{cancel: cancel}, ctx
}
Go
func (g *Group) Go(f func() error) {
g.wg.Add(1)
go func() {
defer g.wg.Done()
if err := f(); err != nil {
g.errOnce.Do(func() {
g.err = err //记录子协程中的错误
if g.cancel != nil {
g.cancel()
}
})
}
}()
}
小结
- errgroup 可以捕获和记录子协程的错误(只能记录最先出错的协程的错误)
- errgroup 可以控制协程并发顺序。确保子协程执行完成后再执行主协程
- errgroup 可以使用 context 实现协程撤销。或者超时撤销。子协程中使用 ctx.Done()来获取撤销信号