我们可以在Go语言中十分便捷地开启goroutine去并发地执行任务,但是如何有效的处理并发过程中的错误则是一个很棘手的问题,本文介绍了一些处理并发错误的方法。
recover goroutine中的panic
我们知道可以在代码中使用 recover 来会恢复程序中意想不到的 panic,而 panic 只会触发当前 goroutine 中的 defer 操作。
例如在下面的示例代码中,无法在 main 函数中 recover 另一个goroutine中引发的 panic。
执行上面的 f1 函数会得到如下结果:
从输出结果可以看到程序并没有正常退出,而是由于 panic 异常退出了(exit code 2)。
正如上面示例演示的那样,在启用 goroutine 去执行任务的场景下,如果想要 recover goroutine中可能出现的 panic 就需要在 goroutine 中使用 recover。就像下面的 f2 函数那样。
执行 f2 函数会得到如下输出结果。
程序中的 panic 被 recover 成功捕获,程序最终正常退出。
errgroup
在以往演示的并发示例中,我们通常像下面的示例代码那样在 go 关键字后,调用一个函数或匿名函数。
在之前讲解并发的代码示例中我们默认被并发的那些函数都不会返回错误,但真实的情况往往是事与愿违。
当我们想要将一个任务拆分成多个子任务交给多个 goroutine 去运行,这时我们该如何获取到子任务可能返回的错误呢?
假设我们有多个网址需要并发去获取它们的内容,这时候我们会写出类似下面的代码。
执行上述
fetchUrlDemo
函数得到如下输出结果,由于 http://www.yixieqitawangzhi.com 是我随意编造的一个并不真实存在的 url,所以对它的 HTTP 请求会返回错误。在上面的示例代码中,我们开启了 3 个 goroutine 分别去获取3个 url 的内容。类似这种将任务分为若干个子任务的场景会有很多,那么我们如何获取子任务中可能出现的错误呢?
errgroup 包就是为了解决这类问题而开发的,它能为处理公共任务的子任务而开启的一组 goroutine 提供同步、error 传播和基于context 的取消功能。
errgroup 包中定义了一个 Group 类型,它包含了若干个不可导出的字段。
errgroup.Group 提供了
Go
和Wait
两个方法。- Go 函数会在新的 goroutine 中调用传入的函数f。
- 第一个返回非零错误的调用将取消该Group;下面的Wait方法会返回该错误
- Wait 会阻塞直至由上述 Go 方法调用的所有函数都返回,然后从它们返回第一个非nil的错误(如果有)。
下面的示例代码演示了如何使用 errgroup 包来处理多个子任务 goroutine 中可能返回的 error。
执行上面的
fetchUrlDemo2
函数会得到如下输出结果。当子任务的 goroutine 中对
http://www.yixieqitawangzhi.com
发起 HTTP 请求时会返回一个错误,这个错误会由 errgroup.Group 的 Wait 方法返回。通过阅读下方 errgroup.Group 的 Go 方法源码,我们可以看到当任意一个函数 f 返回错误时,会通过
g.errOnce.Do
只将第一个返回的错误记录,并且如果存在 cancel 方法则会调用cancel。那么如何创建带有 cancel 方法的 errgroup.Group 呢?
答案是通过 errorgroup 包提供的 WithContext 函数。
WithContext 函数接收一个父 context,返回一个新的 Group 对象和一个关联的子 context 对象。下面的代码片段是一个官方文档给出的示例。
或者这里有另外一个示例。
可惜这两个示例不太好理解。。。