gocookbook
gocookbook copied to clipboard
并发编程之ErrorGroup--子goroutine的错误传播和取消执行
在并发编程里,sync.WaitGroup
并发原语的使用频率非常高,它经常用于协同等待的场景:goroutine
A 在检查点(checkpoint)等待一组执行任务的 worker goroutine
全部完成,如果在执行任务的这些 goroutine
还没全部完成, goroutine
A 就会阻塞在检查点,直到所有woker goroutine
都完成后才能继续执行。
如果在woker goroutine
的执行过程中遇到错误并想要处理该怎么办?WaitGroup
并没有提供传播错误的功能,遇到这种场景我们改怎么办?Go
语言在扩展库的提供了ErrorGroup
并发原语正好适合在这种场景下使用,它在WaitGroup
的基础上还提供了,错误传播以及上下文取消的功能。
Go
扩展库通过errorgroup.Group
提供ErrorGroup
原语的功能,它有三个方法可调用:
func WithContext(ctx context.Context) (*Group, context.Context)
func (g *Group) Go(f func() error)
func (g *Group) Wait() error
接下来我们让主goroutine
使用ErrorGroup
代替WaitGroup
等待所以子任务的完成,ErrorGroup
有一个特点是会返回所以执行任务的goroutine
遇到的第一个错误。我们试着执行一下下面的程序,观察一下程序的输出。
package main
import (
"fmt"
"log"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
var eg errgroup.Group
for i := 0; i < 100; i++ {
i := i
eg.Go(func() error {
time.Sleep(2 * time.Second)
if i > 90 {
fmt.Println("Error:", i)
return fmt.Errorf("Error occurred: %d", i)
}
fmt.Println("End:", i)
return nil
})
}
if err := eg.Wait(); err != nil {
log.Fatal(err)
}
}
上面程序,遇到i大于90的都会产生错误结束执行,但是只有第一个执行时产生的错误被ErrorGroup
返回,程序的输出大概如下:
......
End: 49
End: 26
Error: 98
End: 63
End: 39
End: 50
End: 38
Error: 95
End: 67
End: 65
End: 57
End: 64
2020/12/17 18:11:40 Error occurred: 98
最早执行遇到错误的goroutine
输出了Error: 98
但是所有未执行完的其他任务并没有停止执行,那么想让程序遇到错误就终止其他子任务该怎么办呢?我们可以用errgroup.Group
提供的WithContext
方法创建一个带可取消上下文功能的ErrorGroup
。
package main
import (
"context"
"fmt"
"log"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
eg, ctx := errgroup.WithContext(context.Background())
for i := 0; i < 100; i++ {
i := i
eg.Go(func() error {
time.Sleep(2 * time.Second)
select {
case <-ctx.Done():
fmt.Println("Canceled:", i)
return nil
default:
if i > 90 {
fmt.Println("Error:", i)
return fmt.Errorf("Error: %d", i)
}
fmt.Println("End:", i)
return nil
}
})
}
if err := eg.Wait(); err != nil {
log.Fatal(err)
}
}
Go
方法单独开启的gouroutine
在执行参数传递进来的函数时,如果函数返回了错误,会对ErrorGroup
持有的err
字段进行赋值并及时调用cancel
函数,通过上下文通知其他子任务取消执行任务。所以上面更新后的程序会员如下类似的输出。
......
Error: 99
Canceled: 68
Canceled: 85
End: 57
End: 51
Canceled: 66
Canceled: 93
Canceled: 72
Canceled: 78
End: 55
Canceled: 74
2020/12/17 18:23:12 Error: 99
使用errorgroup.Group
时注意它的两个特点:
-
errgroup.Group
在出现错误或者等待结束后都会调用Context
对象 的cancel
方法同步取消信号。 - 只有第一个出现的错误才会被返回,剩余的错误都会被直接抛弃。