zoukankan      html  css  js  c++  java
  • go中errgroup源码解读

    errgroup

    前言

    来看下errgroup的实现

    如何使用

    func main() {
    	var eg errgroup.Group
    
    	eg.Go(func() error {
    		return errors.New("test1")
    	})
    
    	eg.Go(func() error {
    		return errors.New("test2")
    	})
    
    	if err := eg.Wait(); err != nil {
    		fmt.Println(err)
    	}
    }
    

    类比于waitgroup,errgroup增加了一个对goroutine错误收集的作用。

    不过需要注意的是:

    errgroup返回的第一个出错的goroutine抛出的err

    errgroup中还可以加入context

    func main() {
    	eg, ctx := errgroup.WithContext(context.Background())
    
    	eg.Go(func() error {
    		// test1函数还可以在启动很多goroutine
    		// 子节点都传入ctx,当test1报错,会把test1的子节点一一cancel
    		return test1(ctx)
    	})
    
    	eg.Go(func() error {
    		return test1(ctx)
    	})
    
    	if err := eg.Wait(); err != nil {
    		fmt.Println(err)
    	}
    }
    
    func test1(ctx context.Context) error {
    	return errors.New("test2")
    }
    

    实现原理

    代码很简单

    type Group struct {
    	// 一个取消的函数,主要来包装context.WithCancel的CancelFunc
    	cancel func()
    
    	// 还是借助于WaitGroup实现的
    	wg sync.WaitGroup
    
    	// 使用sync.Once实现只输出第一个err
    	errOnce sync.Once
    
    	// 记录下错误的信息
    	err     error
    }
    

    还是在WaitGroup的基础上实现的

    WithContext

    // 返回一个被context.WithCancel重新包装的ctx
    
    func WithContext(ctx context.Context) (*Group, context.Context) {
    	ctx, cancel := context.WithCancel(ctx)
    	return &Group{cancel: cancel}, ctx
    }
    

    里面使用了context,通过context.WithCancel对传入的context进行了包装

    WithCancel函数返回的CancelFunc被调用或者是父节点的done channel被关闭(父节点的 CancelFunc 被调用),此 context(子节点)的 done channel 也会被关闭。

    errgroup把返回的CancelFunc包进了自己的cancel中,来实现对使用errgroupctx启动的goroutine的取消操作。

    Go

    // 启动取消阻塞的goroutine
    // 记录第一个出错的goroutine的err信息
    func (g *Group) Go(f func() error) {
    	// 借助于waitgroup实现
    	g.wg.Add(1)
    
    	go func() {
    		defer g.wg.Done()
    
    		// 执行出错
    		if err := f(); err != nil {
    			// 通过sync.Once记录下第一个出错的err信息
    			g.errOnce.Do(func() {
    				g.err = err
    				// 如果包装了cancel,也就是context的CancelFunc,执行退出操作
    				if g.cancel != nil {
    					g.cancel()
    				}
    			})
    		}
    	}()
    }
    

    1、借助于waitgroup实现对goroutine阻塞;

    2、通过sync.Once记录下,第一个出错的goroutine的错误信息;

    3、如果包装了contextCancelFunc,在出错的时候进行退出操作。

    Wait

    // 阻塞所有的通过Go加入的goroutine,然后等待他们一个个执行完成
    // 然后返回第一个出错的goroutine的错误信息
    func (g *Group) Wait() error {
    	// 借助于waitgroup实现
    	g.wg.Wait()
    	// 如果包装了cancel,也就是context的CancelFunc,执行退出操作
    	if g.cancel != nil {
    		g.cancel()
    	}
    	return g.err
    }
    

    1、借助于waitgroup实现对goroutine阻塞;

    2、如果包装了contextCancelFunc,在出错的时候进行退出操作;

    3、抛出第一个出错的goroutine的错误信息。

    错误的使用

    不过工作中发现一个errgroup错误使用的例子

    func main() {
    	eg := errgroup.Group{}
    	var err error
    	eg.Go(func() error {
    		// 处理业务
    		err = test1()
    		return err
    	})
    
    	eg.Go(func() error {
    		// 处理业务
    		err = test1()
    		return err
    	})
    
    	if err = eg.Wait(); err != nil {
    		fmt.Println(err)
    	}
    }
    
    func test1() error {
    	return errors.New("test2")
    }
    

    很明显err被资源竞争了

    $ go run -race main.go 
    ==================
    WARNING: DATA RACE
    Write at 0x00c0000801f0 by goroutine 8:
      main.main.func2()
          /Users/yj/Go/src/Go-POINT/sync/errgroup/main.go:23 +0x97
    ...
    

    总结

    errgroup相比比较简单,不过需要先弄明白waitgroup,context以及sync.Once,主要是借助这几个组件来实现的。

    errgroup可以带携带context,如果包装了context,会使用context.WithCancel进行超时,取消或者一些异常的情况

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://www.cnblogs.com/ricklz/p/14500392.html

  • 相关阅读:
    Fix Installing .NET Framework 3.5 failed Error Code 0x800F0954 on Windows 10
    RHEL8安装五笔输入法
    Enable EPEL and Local Repository on RHEL8
    Why is Yum Replaced by DNF?
    检查Linux服务器是否被攻击的常用命令及方法
    IDEA 主题
    IDEA 如何显示一个类中所有的方法
    Appium 安装以及安装过程中遇到的问题
    Maven 如何发布 jar 包到 Nexus 私库
    java泛型的基本使用
  • 原文地址:https://www.cnblogs.com/ricklz/p/14500392.html
Copyright © 2011-2022 走看看