errgroup
前言
來看下errgroup的實現(xiàn)
如何使用
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函數(shù)還可以在啟動很多goroutine
// 子節(jié)點都傳入ctx,當(dāng)test1報錯,會把test1的子節(jié)點一一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")
}
實現(xiàn)原理
代碼很簡單
type Group struct {
// 一個取消的函數(shù),主要來包裝context.WithCancel的CancelFunc
cancel func()
// 還是借助于WaitGroup實現(xiàn)的
wg sync.WaitGroup
// 使用sync.Once實現(xiàn)只輸出第一個err
errOnce sync.Once
// 記錄下錯誤的信息
err error
}
還是在WaitGroup的基礎(chǔ)上實現(xiàn)的
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進(jìn)行了包裝
當(dāng)WithCancel函數(shù)返回的CancelFunc被調(diào)用或者是父節(jié)點的done channel被關(guān)閉(父節(jié)點的 CancelFunc 被調(diào)用),此 context(子節(jié)點)的 done channel 也會被關(guān)閉。
errgroup把返回的CancelFunc包進(jìn)了自己的cancel中,來實現(xiàn)對使用errgroup的ctx啟動的goroutine的取消操作。
Go
// 啟動取消阻塞的goroutine
// 記錄第一個出錯的goroutine的err信息
func (g *Group) Go(f func() error) {
// 借助于waitgroup實現(xiàn)
g.wg.Add(1)
go func() {
defer g.wg.Done()
// 執(zhí)行出錯
if err := f(); err != nil {
// 通過sync.Once記錄下第一個出錯的err信息
g.errOnce.Do(func() {
g.err = err
// 如果包裝了cancel,也就是context的CancelFunc,執(zhí)行退出操作
if g.cancel != nil {
g.cancel()
}
})
}
}()
}
1、借助于waitgroup實現(xiàn)對goroutine阻塞;
2、通過sync.Once記錄下,第一個出錯的goroutine的錯誤信息;
3、如果包裝了context的CancelFunc,在出錯的時候進(jìn)行退出操作。
Wait
// 阻塞所有的通過Go加入的goroutine,然后等待他們一個個執(zhí)行完成
// 然后返回第一個出錯的goroutine的錯誤信息
func (g *Group) Wait() error {
// 借助于waitgroup實現(xiàn)
g.wg.Wait()
// 如果包裝了cancel,也就是context的CancelFunc,執(zhí)行退出操作
if g.cancel != nil {
g.cancel()
}
return g.err
}
1、借助于waitgroup實現(xiàn)對goroutine阻塞;
2、如果包裝了context的CancelFunc,在出錯的時候進(jìn)行退出操作;
3、拋出第一個出錯的goroutine的錯誤信息。
錯誤的使用
不過工作中發(fā)現(xiàn)一個errgroup錯誤使用的例子
func main() {
eg := errgroup.Group{}
var err error
eg.Go(func() error {
// 處理業(yè)務(wù)
err = test1()
return err
})
eg.Go(func() error {
// 處理業(yè)務(wù)
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
...
總結(jié)
errgroup相比比較簡單,不過需要先弄明白waitgroup,context以及sync.Once,主要是借助這幾個組件來實現(xiàn)的。
errgroup可以帶攜帶context,如果包裝了context,會使用context.WithCancel進(jìn)行超時,取消或者一些異常的情況
|