來看下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
錯誤收集的做用。code
不過須要注意的是:資源
errgroup
返回的第一個出錯的goroutine
拋出的err
。it
errgroup
中還能夠加入context
class
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") }
代碼很簡單test
type Group struct { // 一個取消的函數,主要來包裝context.WithCancel的CancelFunc cancel func() // 仍是藉助於WaitGroup實現的 wg sync.WaitGroup // 使用sync.Once實現只輸出第一個err errOnce sync.Once // 記錄下錯誤的信息 err error }
仍是在WaitGroup的基礎上實現的基礎
// 返回一個被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
也會被關閉。channel
errgroup
把返回的CancelFunc
包進了本身的cancel
中,來實現對使用errgroup
的ctx
啓動的goroutine
的取消操做。error
// 啓動取消阻塞的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() } }) } }() }
一、藉助於waitgroup
實現對goroutine
阻塞;
二、經過sync.Once
記錄下,第一個出錯的goroutine
的錯誤信息;
三、若是包裝了context
的CancelFunc
,在出錯的時候進行退出操做。
// 阻塞全部的經過Go加入的goroutine,而後等待他們一個個執行完成 // 而後返回第一個出錯的goroutine的錯誤信息 func (g *Group) Wait() error { // 藉助於waitgroup實現 g.wg.Wait() // 若是包裝了cancel,也就是context的CancelFunc,執行退出操做 if g.cancel != nil { g.cancel() } return g.err }
一、藉助於waitgroup
實現對goroutine
阻塞;
二、若是包裝了context
的CancelFunc
,在出錯的時候進行退出操做;
三、拋出第一個出錯的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
進行超時,取消或者一些異常的狀況