go中errgroup源碼解讀,第1張

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進行超時,取消或者一些異常的情況


生活常識_百科知識_各類知識大全»go中errgroup源碼解讀

0條評論

    發表評論

    提供最優質的資源集郃

    立即查看了解詳情