// 案例一:通过 groutine 显示返回 err 触发 errgroup.Group 底层的 cancel 方法
func main() {
ctx := context.Background()
eg, ctx := errgroup.WithContext(ctx)
for i := 0; i < 10; i++ {
i := i // 这里需要进行赋值操作,不然会有闭包问题,eg.Go 执行的 groutine 会引用 for 循环的 i
eg.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(time.Duration(i) * time.Second):
}
if i == 2 {
return fmt.Errorf("i can't be 2") // 需要返回 err 才会导致 eg 的 cancel 方法
}
fmt.Println(i)
return nil
})
}
if err := eg.Wait(); err != nil {
fmt.Println(err.Error())
}
}
// 打印结果:
0
1
i can't be 2
// 案例二:通过显示调用 cancel 方法通知到各个 groutine 退出
func main() {
ctx, cancel := context.WithCancel(context.Background())
eg, ctx := errgroup.WithContext(ctx)
for i := 0; i < 10; i++ {
i := i // 这里需要进行赋值操作,不然会有闭包问题,eg.Go 执行的 groutine 会引用 for 循环的 i
eg.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(time.Duration(i) * time.Second):
}
if i == 2 {
cancel()
return nil // 可以不用返回 err,因为手动触发了 cancel 方法
//return fmt.Errorf("i can't be 2")
}
fmt.Println(i)
return nil
})
}
if err := eg.Wait(); err != nil {
fmt.Println(err.Error())
}
}
// 打印结果:
0
1
context canceled
// 案例三:
// 基于 errgroup 实现一个 http server 的启动和关闭 ,以及 linux signal 信号的注册和处理,要保证能够 一个退出,全部注销退出
func main() {
g, ctx := errgroup.WithContext(context.Background())
mux := http.NewServeMux()
mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
// 模拟单个服务错误退出
serverOut := make(chan struct{})
mux.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
serverOut <- struct{}{}
})
server := http.Server{
Handler: mux,
Addr: ":8080",
}
// g1
// g1 退出了所有的协程都能退出么?
// g1 退出后, context 将不再阻塞,g2, g3 都会随之退出
// 然后 main 函数中的 g.Wait() 退出,所有协程都会退出
g.Go(func() error {
return server.ListenAndServe()
})
// g2
// g2 退出了所有的协程都能退出么?
// g2 退出时,调用了 shutdown,g1 会退出
// g2 退出后, context 将不再阻塞,g3 会随之退出
// 然后 main 函数中的 g.Wait() 退出,所有协程都会退出
g.Go(func() error {
select {
case <-ctx.Done():
log.Println("errgroup exit...")
case <-serverOut:
log.Println("server will out...")
}
timeoutCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
// 这里不是必须的,但是如果使用 _ 的话静态扫描工具会报错,加上也无伤大雅
defer cancel()
log.Println("shutting down server...")
return server.Shutdown(timeoutCtx)
})
// g3
// g3 捕获到 os 退出信号将会退出
// g3 退出了所有的协程都能退出么?
// g3 退出后, context 将不再阻塞,g2 会随之退出
// g2 退出时,调用了 shutdown,g1 会退出
// 然后 main 函数中的 g.Wait() 退出,所有协程都会退出
g.Go(func() error {
quit := make(chan os.Signal, 0)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
select {
case <-ctx.Done():
return ctx.Err()
case sig := <-quit:
return errors.Errorf("get os signal: %v", sig)
}
})
fmt.Printf("errgroup exiting: %+v\n", g.Wait())
}