这篇文章主要讲的是如何去处理并发的错误。
在Go语言中十分便捷地开启goroutine去并发地执行任务,但是如何有效的处理并发过程中的错误则是一个很棘手的问题。
文章目录
- recover
- errgroup
recover
哦对,似乎没写错误处理的文章。后面补上。
首先,这里的recover通常用来错误处理。
我们可以在代码中使用 recover 来会恢复程序中弹出的 panic,而 panic 只会触发当前 goroutine 中的 defer 操作。
func f1() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("recover panic:%v\n", e)
}
}()
// 开启一个goroutine执行任务
go func() {
fmt.Println("in goroutine....")
// 只能触发当前goroutine中的defer
panic("panic in goroutine")
}()
time.Sleep(time.Second)
fmt.Println("exit")
}
func main() {
f1()
}
从输出结果可以看到程序并没有正常退出,而是由于 panic 异常退出了(exit code 2)。
正如上面示例演示的那样,在启用 goroutine
去执行任务的场景下,如果想要 recover goroutine
中可能出现的 panic
就需要在 goroutine
中使用 recover
。
程序中的 panic 被 recover 成功捕获
,程序最终正常退出。
errgroup
我们通常在使用goroutine
是 调用一个函数或匿名函数
go func(){
// ...
}
go foo()
并发的那些函数,其实很难拿的准。错误信息只多不少。
当我们想要将一个任务
拆分成多个子任务
交给多个 goroutine
去运行,这时我们该如何获取到子任务可能返回的错误呢?
// fetchUrlDemo 并发获取url内容
func FetchUrlDemo() {
wg := sync.WaitGroup{}
var urls = []string{
"http://www.baidu.com",
"http://www.qq.com",
"http://www.nihaosfasdfasdf.com",
}
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
resp, err := http.Get(url)
if err == nil {
fmt.Printf("获取%s成功\n", url)
resp.Body.Close()
}
return // 如何将错误返回呢?
}(url)
}
wg.Wait()
// 如何获取goroutine中可能出现的错误呢?
}
上面的示例代码中,我们开启了 3 个 goroutine 分别去获取3个 url 的内容。
类似这种将任务分为若干个子任务的场景会有很多,那么我们如何获取子任务中可能出现的错误呢?
errgroup 包就是为了解决这类问题而开发的.
它能为处理公共任务的子任务而开启的一组 goroutine 提供同步
、error 传播
和基于context
的取消功能。
errgroup
包中定义了一个 Group 类型
,它包含了若干个不可导出
的字段。
type Group struct {
cancel func()
wg sync.WaitGroup
errOnce sync.Once
err error
}
errgroup.Group
提供了Go
和Wait
两个方法。
Go : func (g *Group) Go(f func() error)
-
Go 函数会在新的 goroutine 中调用传入的函数f。
-
第一个返回非零错误的调用将取消该Group;下面的Wait方法会返回该错误
Wait:func (g *Group) Wait() error
Wait
会阻塞直至由上述Go 方法
调用的所有函数都返回,然后从它们返回第一个非nil的错误(如果有)
// fetchUrlDemo2 使用errgroup并发获取url内容
func fetchUrlDemo2() error {
g := new(errgroup.Group) // 创建等待组(类似sync.WaitGroup)
var urls = []string{
"http://www.4399.com",
"http://www.baidu.com",
"http://www.sdhfjoahoesrh.com",
}
for _, url := range urls {
url := url // 注意此处声明新的变量
// 启动一个goroutine去获取url内容
g.Go(func() error {
resp, err := http.Get(url)
if err == nil {
fmt.Printf("获取%s成功\n", url)
resp.Body.Close()
}
return err // 返回错误
})
}
if err := g.Wait(); err != nil {
// 处理可能出现的错误
fmt.Println(err)
return err
}
fmt.Println("所有goroutine均成功")
return nil
}
func main() {
fetchUrlDemo2()
}
当子任务的 goroutine 中对,http://www.sdhfjoahoesrh.com
,发起 HTTP 请求时会返回一个错误,这个错误会由 errgroup.Group
的 Wait 方法
返回。
通过阅读 errgroup.Group 的 Go 方法源码
,我们可以看到当任意一个函数 f 返回错误时,会通过g.errOnce.Do
只将第一个返回
的错误记录,并且如果存在 cancel 方法
则会调用cancel
。
那么如何创建带有 cancel 方法的 errgroup.Group ?
func WithContext(ctx context.Context) (*Group, context.Context)
WithContext 函数
接收一个父 context
,返回一个新的 Group 对象
和一个关联的子 context 对象
。