240 likes | 268 Views
This article explores the source code of sync errgroup, a Go package for handling a group of goroutines, and discusses its design and implementation principles. It also provides examples and tips for using sync errgroup effectively in real-world scenarios.
E N D
sync.errgroup 源码阅读 mai 2019-07-15
自我介绍 个人 • POP IM 后端负责人 • Go 夜读发起人 • TUG 华南区负责人 因缘 • 从 Go 源码阅读中学习设计和实现原理 • 更多 reading-go 我能提供的 • Go 夜读持续阅读分享 • Go 实战经验交流 • 帮助更多的开发者 • TiDB User Group 用户生态 我想获得的 • Go 技术提升 • Go 实战经验 • 用户和开发者
目录 • sync.errgroup 的由来 • sync.errgroup 的示例 • sync.errgroup 源码阅读 • sync.errgroup 延展 kratos/pkg/sync/errgroup • sync.errgroup 开源项目使用情况 • Q&A
WaitGroup 处理并发任务 创建 goroutine ,开始任务调用 wg.Add(1); 任务结束,调用 wg.Done(); 等待所有任务完成,调用 wg.Wait()
WaitGroup 的问题 当我们的 goroutine 出错了,我们怎么知道出错的原因呢?
sync.errgroup 的由来 errgroup 是为了处理一组任务的子任务的 goroutine 组,它提供同步、错误传递和上下文取消。
听不懂啊! 你有一组任务是并发的工作,当遇到某种错误或者你不想再输出了,你可能想取消整个 goroutine,那么 errgroup.errgroup 就是为你而设计的。
目录 • sync.errgroup 的由来 • sync.errgroup 的示例 • sync.errgroup 源码阅读 • sync.errgroup 延展 kratos/pkg/sync/errgroup • sync.errgroup 开源项目使用情况 • Q&A
示例:并发爬虫 var g errgroup.Group var urls = []string{ "http://www.baidu.com/", "http://www.baidu.com/", "http://www.1234567.com/",//假的 } for _, url := range urls { // Launch a goroutine to fetch the URL. url := url g.Go(func() error { // Fetch the URL. resp, err := http.Get(url) if err == nil { // 这里记得关掉 resp.Body.Close() } // .. 这里可以做一些 resp 的处理 return err }) } // Wait for all HTTP fetches to complete. err := g.Wait(); if err != nil { fmt.Printf("err: %v", err) return } fmt.Println("Successfully fetched all URLs.") 协程内出错并不会影响其他协程的的处理
目录 • sync.errgroup 的由来 • sync.errgroup 的示例 • sync.errgroup 源码阅读 • sync.errgroup 延展 kratos/pkg/sync/errgroup • sync.errgroup 开源项目使用情况 • Q&A
sync.errgroup 源码阅读 // Group 是处理同一总体任务的子任务的 goroutines 集合 type Group struct { cancel func() // context 中的 cancel wg sync.WaitGroup // 多个任务调度 errOnce sync.Once // 每个只执行一次 err error } // WithContext 返回一个从 ctx 派生的 context 的新的 Group // 当函数第一次传递给 Go 时,派生 context 被取消,返回非零错误或第一次 Wait 返回,以发生者为优先。 func WithContext(ctx context.Context) (*Group, context.Context) { ctx, cancel := context.WithCancel(ctx) return &Group{cancel: cancel}, ctx }
sync.errgroup 源码阅读 // 利用 waitGroup 的 wait func (g *Group) Wait() error { g.wg.Wait() if g.cancel != nil { g.cancel() } return g.err } ...
sync.errgroup 源码阅读 // Go 在一个新的 goroutine 里面调用给的 function // 第一次调用返回一个非零值 error,它的错误将会被返回在 Wait 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() } }) } }() }
目录 • sync.errgroup 的由来 • sync.errgroup 的示例 • sync.errgroup 源码阅读 • sync.errgroup 延展 kratos/pkg/sync/errgroup • sync.errgroup 开源项目使用情况 • Q&A
kratos 之 sync/errgroup 提供带 recover 和并行数的 errgroup,err 中包含详细堆栈信息。
kratos 之 sync/errgroup 使用 见 errgroup/doc.go
kratos 的 pkg/sync/errgroup typeGroupstruct { err error wg sync.WaitGroup errOnce sync.Once workerOnce sync.Once ch chanfunc(ctx context.Context) error chs []func(ctx context.Context) error ctx context.Context cancel func() } 底色部分为 kratos 相对于 sync/errgroup 新增
kratos pkg/sync/errgroup 获取堆栈信息 deferfunc() { if r :=recover(); r != nil { buf :=make([]byte, 64<<10) buf = buf[:runtime.Stack(buf, false)] err = fmt.Errorf("errgroup: panic recovered: %s\n%s", r, buf) } if err != nil { g.errOnce.Do(func() { g.err = err if g.cancel != nil { g.cancel() } }) } g.wg.Done() // 因为是并发,所以这里需要捕获并执行 Done() }()
目录 • sync.errgroup 的由来 • sync.errgroup 的示例 • sync.errgroup 源码阅读 • sync.errgroup 延展 kratos/pkg/sync/errgroup • sync.errgroup 开源项目使用情况 • Q&A
开源项目使用情况 • 基于 errgroup 的封装 https://godoc.org/github.com/cockroachdb/cockroach/pkg/util/ctxgroup • https://godoc.org/github.com/coreos/etcdlabs/cluster • https://godoc.org/github.com/docker/containerd/fs • https://godoc.org/github.com/grafana/grafana/pkg/tsdb/cloudwatch • https://godoc.org/github.com/knative/serving/pkg/pool • https://godoc.org/github.com/kubernetes/minikube/pkg/minikube/bootstrapper/kubeadm
开源项目使用情况 #etcdlabs/cluster/cluster.go // WaitForLeader waits for cluster to elect a new leader. func (clus *Cluster) WaitForLeader() error { lg.Info("wait for leader election") var g errgroup.Group for i := 0; i < clus.size; i++ { idx := i g.Go(func() error { return clus.Members[idx].WaitForLeader() }) } if gerr := g.Wait(); gerr != nil { return gerr } lg.Info("waited for leader election") … }
参考资料 • https://godoc.org/golang.org/x/sync/errgroup • https://studygolang.com/articles/4189 • x/sync/errgroup: get all errors https://github.com/golang/go/issues/23595
Go 夜读的成长离不开大家的支持! 希望你可以给予真实的反馈! 问卷调查
Q&A Thanks!