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()来获取撤销信号

参考

errgroup godoc

posted @ 2019-07-16 11:32  jssyjam  阅读(1341)  评论(0编辑  收藏  举报