// 案例一:通过 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())
}