Golang Concurrency:sync.errgroup

简介: Golang Concurrency:sync.errgroup

背景

在开发中,经常会遇到一个接口需要组合多种数据返回的情况,并且所有数据必须全部成功,否则就需要返回错误。以下图的微博举例:

网络异常,图片无法展示
|

一条微博由两部分数据组成:博主 + 微博。如果需要完整显示这条微博,两者缺一不可。微博的URL为 https://weibo.com/1227368500/H3GIgngon,构成为:协议://域名/用户ID/微博mid。由于提前知道 博主ID微博ID,因此在读取的时候可以并发两个请求分别获取 博主微博 数据。

并发控制

在并发获取数据的时候,会遇到两种异常情况:

  1. 请求出错
  2. 请求超时

由于两部分数据缺一不可,无论哪个请求出现异常,此时再继续处理该请求都毫无意义,此时最好的做法是:

  1. 停止仍在处理中的请求
  2. 将第一个异常返回给用户

快速失败(Fail-fast) 带来的好处显而易见:

  1. 停止无效的请求,减少资源资源
  2. 快速返回,方便用户再次重试,降低偶然出错的影响

sync.errgroup

那么,Go 语言中该怎么实现呢? golang 提供了一个很好用的小组件:errgroup。仍然以微博为例,来演示如何使用该函数:

package main
import (
  "context"
  "fmt"
  "golang.org/x/sync/errgroup"
  "os"
)
var (
  Blogger   = find("1227368500")
  Weibo = find("H3GIgngon")
)
type Result string
type Find func(ctx context.Context, query string) (Result, error)
func find(kind string) Find {
  return func(_ context.Context, query string) (Result, error) {
    return Result(fmt.Sprintf("%s result for %q", kind, query)), nil
  }
}
func main() {
  SinaWeibo := func(ctx context.Context, query string) ([]Result, error) {
    g, ctx := errgroup.WithContext(ctx)
    finds := []Find{Blogger, Weibo}
    results := make([]Result, len(finds))
    for i, find := range finds {
      i, find := i, find // https://golang.org/doc/faq#closures_and_goroutines
      g.Go(func() error {
        result, err := find(ctx, query)
        if err == nil {
          results[i] = result
        }
        return err
      })
    }
    if err := g.Wait(); err != nil {
      return nil, err
    }
    return results, nil
  }
  results, err := SinaWeibo(context.Background(), "https://weibo.com/1227368500/H3GIgngon")
  if err != nil {
    fmt.Fprintln(os.Stderr, err)
    return
  }
  for _, result := range results {
    fmt.Println(result)
  }
}

errgroup 使用 Context 来控制超时,使用 Go() 函数返回的第一个错误来停止所有协程。使用 errgroup 尤其需要小心踩坑 闭包 问题

源码解析

那么 errgroup 究竟是如何实现的呢?errgroup 简单的令人惊讶,却功能强大。代码+注释不过六十余行,值得仔细品味。源码如下:

// Package errgroup provides synchronization, error propagation, and Context
// cancelation for groups of goroutines working on subtasks of a common task.
package errgroup
import (
  "context"
  "sync"
)
// A Group is a collection of goroutines working on subtasks that are part of
// the same overall task.
//
// A zero Group is valid and does not cancel on error.
type Group struct {
  cancel func()
  wg sync.WaitGroup
  errOnce sync.Once
  err     error
}
// WithContext returns a new Group and an associated Context derived from ctx.
//
// The derived Context is canceled the first time a function passed to Go
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithContext(ctx context.Context) (*Group, context.Context) {
  ctx, cancel := context.WithCancel(ctx)
  return &Group{cancel: cancel}, ctx
}
// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
func (g *Group) Wait() error {
  g.wg.Wait()
  if g.cancel != nil {
    g.cancel()
  }
  return g.err
}
// Go calls the given function in a new goroutine.
//
// The first call to return a non-nil error cancels the group; its error will be
// returned by 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()
        }
      })
    }
  }()
}

问题

  1. 除了官方实现,其实 bilbil 也有类似实现 bilbil/kratos。为什么官方库却没有选择实现以下两个功能?:
  • 最大并发限制
  • panic 恢复
  1. 同样以微博为例,用户访问微博的时候,同样需要获取“评论数”、“点赞数”、“是否已关注”。不同的是,这些数据并非关键数据,获取失败却不影响用户查看微博。因此,在官宣恋情婚讯 的高峰期,其实可以尽力提供服务。那么非关键数据,如何实现 尽力 并发获取?

本文作者 : cyningsun

本文地址https://www.cyningsun.com/12-30-2020/golang-errgroup.html

版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# Golang

  1. 译|There Are No Reference Types in Go
  2. Go 语言没有引用类型,指针也与众不同
  3. 译|What “accept interfaces, return structs” means in Go
  4. 如何用好 Go interface
  5. 一个优雅的 LRU 缓存实现
目录
相关文章
|
Android开发 Shell
Android模拟点击的四种方式
导论 在Android中模拟一个点击事件有三种方式是通过模拟MotionEvent来实现;一种是通过ADB来实现;一种是通过Instrumentation测试框架来实现 第一种:模拟MotionEvent 通用方法如下: private void setSimulateClick(View ...
7644 0
|
NoSQL 算法 JavaScript
Redis 实现限流的三种方式
Redis 实现限流的三种方式
|
IDE 开发工具 Python
PyCharm IDEA 安装【Chinese(Simplified)Language Pack/中文语言包】插件汉化出错
安装【Chinese(Simplified)Language Pack/中文语言包】插件时报【Plugin Installation】错误
8014 1
PyCharm IDEA 安装【Chinese(Simplified)Language Pack/中文语言包】插件汉化出错
|
5月前
2025年阿里云域名备案流程(图文详细教程)
本文详细介绍了2025年阿里云域名备案的全流程,包括注册阿里云账号、企业实名认证、购买服务器、创建域名信息模板、购买域名、域名备案及查询备案号等步骤。通过图文结合的方式,清晰展示了每个环节的操作方法和注意事项,帮助用户顺利完成域名备案。文章强调了域名备案的前提是国内需有一台服务器,并提供了具体配置建议,同时提醒用户注意邮箱验证和短信核验等关键步骤,确保备案顺利通过。
6323 13
|
9月前
|
存储 数据采集 大数据
数据仓库建模规范思考
本文介绍了数据仓库建模规范,包括模型分层、设计、数据类型、命名及接口开发等方面的详细规定。通过规范化分层逻辑、高内聚松耦合的设计、明确的命名规范和数据类型转换规则,提高数据仓库的可维护性、可扩展性和数据质量,为企业决策提供支持。
709 10
|
11月前
|
Java
为什么Java不支持多继承
本文讨论了Java不支持多继承的原因,包括避免菱形继承问题、简化编程语言和防止层次膨胀,同时提供了实现多继承效果的替代方案,如实现多接口、使用组合和继承加接口的方式。
248 0
为什么Java不支持多继承
使用delve调试golang
使用delve调试golang
161 0
【ClickHouse】深入浅出系列之配置详解,全中文注释!
【ClickHouse】深入浅出系列之配置详解,全中文注释!
|
供应链 监控 搜索推荐
ERP系统中的订单管理与供应链协作解析
【7月更文挑战第25天】 ERP系统中的订单管理与供应链协作解析
892 6
|
存储 Kubernetes API
Kubernetes 1.26版本更新解读
Kubernetes 1.26版本更新解读