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 缓存实现
目录
相关文章
|
6月前
|
Go
浅谈Golang广播sync.Cond
浅谈Golang广播sync.Cond
104 0
|
6月前
|
Go
浅谈Golang并发控制WaitGroup
浅谈Golang并发控制WaitGroup
56 0
|
3月前
|
存储 缓存 安全
go sync.Pool 设计与实现
go sync.Pool 设计与实现
37 2
|
3月前
|
设计模式 Java Go
Go - 使用 sync.WaitGroup 来实现并发操作
Go - 使用 sync.WaitGroup 来实现并发操作
44 2
|
3月前
|
Go 人工智能
深入理解 go sync.Waitgroup
深入理解 go sync.Waitgroup
32 0
|
3月前
|
安全 编译器 数据库连接
深入理解 go sync.Once
深入理解 go sync.Once
37 0
|
3月前
|
人工智能 Go
深入理解 go sync.Cond
深入理解 go sync.Cond
58 0
|
6月前
|
Go
Golang标准库sync的使用
Golang标准库sync的使用
57 2
|
6月前
|
存储 安全 Go
Golang深入浅出之-原子操作包(sync/atomic)在Go中的应用
【4月更文挑战第23天】Go语言的`sync/atomic`包支持原子操作,防止多线程环境中的数据竞争。包括原子整数和指针操作,以及原子标量函数。常见问题包括误用非原子操作、误解原子操作语义和忽略内存排序约束。解决方法是使用原子函数、结合其他同步原语和遵循内存约束。注意始终使用原子操作处理共享变量,理解其语义限制,并熟悉内存排序约束,以实现并发安全和高效的应用程序。
84 1
|
Go
Go并发控制sync.WaitGroup
Go语言使用sync.WaitGroup控制并发的顺序,用于任务的同步,等待所有并发任务完成,再持续执行。
69 0