【译】Go:管理多个错误

原文:https://medium.com/a-journey-with-go/go-multiple-errors-management-a67477628cf1

​ 关于开发者使用Go遇到的最大挑战的年度调查报告中,错误管理是经常被争论和反复提起的话题。然而,当涉及到在并发环境中处理错误或为相同的 goroutine 组合多个错误时,Go 提供了很好的包,使多个错误的管理变得很容易

单个 goroutine,多个错误

例如,当您处理具有重试策略的代码时,将多个错误合并成一个非常有用。下面是一个基本的例子,其中我们需要收集生成的错误:

var data = []byte(
`a,b,c
foo
1,2,3
,",
`)

func main() {
  reader := csv.NewReader(bytes.NewBuffer(data))
  for {
    if _, err := reader.Read(); err != nil {
      if err == io.EOF {
        break
      }
      log.Printf(err.Error())
    }
  }
}

上面的程序读取、解析一个 CSV 文件,并且显示报错信息。它可以更方便的对错误进行分组以获得完整的报告。为了将错误合并成一个,我们需要在两个很优秀的包中选择一个:

  • 使用 HashiCorp 的 go-multierror ,可以将错误合并为一个标准错误

    func main() {
      var errs error
      reader := csv.NewReader(bytes.NewBuffer(data))
      for {
        if _, err := reader.Read(); err != nil {
          if err == io.EOF {
            break
          }
          errs = multierror.Append(errs, err)
        }
      }
      if errs != nil {
        log.Printf(errs.Error())
      }
    }
    

    接着错误报告可以被打印出来

    image-20210701193954735

  • 使用 Uber 的 multierr

    代码的实现是类似的,下面是输出

    image-20210701194136437

    错误通过分号连接,没有任何其他格式。

    关于每个包的性能,下面是一个具有较高失败次数的程序的基准测试

    name                    time/op         alloc/op        allocs/op
    HashiCorpMultiErrors-4  6.01µs ± 1%     6.78kB ± 0%     77.0 ± 0%
    UberMultiErrors-4       9.26µs ± 1%     10.3kB ± 0%      126 ± 0%
    

    Uber 的速度稍慢,占用内存更多。但是,这个包的设计目的是在收集到错误之后将其分组,而不是每次都添加错误。当对错误进行分组时,结果很接近,但是代码不够优雅,因为它需要额外的步骤。以下是新的测试结果:

    name                    time/op         alloc/op        allocs/op
    HashiCorpMultiErrors-4  6.01µs ± 1%     6.78kB ± 0%     77.0 ± 0%
    UberMultiErrors-4       6.02µs ± 1%     7.06kB ± 0%     77.0 ± 0%
    
    

    这两个包都利用了 Go 错误接口,它们都实现了自己的 Error() string 函数。

一个错误, 多个 goroutines

当多个 goroutine 来处理一个任务时,正确管理结果和聚合错误以确保程序的正确性是必要的。

让我们从一个使用多个 goroutine 执行一系列操作的程序开始,每一个操作都持续一秒:

func main() {
  var wg sync.WaitGroup
  for i := 0; i < 4; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      if err := action(); err != nil {
        return
      }
      if err := action(); err != nil {
        return
      }
      if err := action(); err != nil {
        return
      }
    }()
  }
  wg.Wait()
}

为了解释错误的传播,第三个 goroutine 的第一个操作会失败,下面时正在发生的事情:

image-20210701195502900

正如预期的那样,程序大约需要 3 秒钟,因为大多数 goroutine 需要经历三个操作,每个操作花费一秒:

go run .  0.30s user 0.19s system 14% cpu 3.274 total

然而,我们可能希望让这些 goroutine 互相依赖,并在其中一个失败时取消它们以避免不必要的工作,解决方案是添加一个上下文,一旦 goroutine 失败,它就会取消它。

image-20210701195747187

这个功能这是 errgroup 包所提供的;当一组 goroutine 工作时的错误和上下文传播。

下面时使用 errgroup包的代码:

func main() {
  g, ctx := errgroup.WithContext(context.Background())
  for i := 0; i < 4; i++ {
    g.Go(func () error {
      if err := action(ctx); err != nil {
        return err
      }
      if err := action(ctx); err != nil {
        return err
      }
      if err := action(ctx); err != nil {
        return err
      }
      return nil
    })
  }
  if err := g.Wait(); err != nil {
    log.Printf(err.Error())
  }
}

现在程序运行的更快了,因为它在发生错误时传播了取消上下文:

go run .  0.30s user 0.19s system 38% cpu 1.269 total

该包提供的另一个好处是,我们不再需要担心 goroutine 的添加和对 goroutine 标记完成。包替我们管理这些, 我们只需要等待程序完成并结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值