mohuishou / blogComment

mohuishou's blog comment
0 stars 0 forks source link

post/go-training-week3-errgroup #241

Closed utterances-bot closed 2 years ago

utterances-bot commented 3 years ago

Go并发编程(七) 深入理解 errgroup - Mohuishou

mohuishou 的 技术博客, 关注云原生, Go, K8s, Docker, 微服务等技术

https://lailin.xyz/post/go-training-week3-errgroup.html

slzheng2017 commented 3 years ago

大哥,我有这样的问题,errgroup可以做到当一个goroutine出错时返回错误,但我还希望每个goroutine能够返回额外的数据,比如一个res 结构体,这个该如何做啊

mohuishou commented 3 years ago

大哥,我有这样的问题,errgroup可以做到当一个goroutine出错时返回错误,但我还希望每个goroutine能够返回额外的数据,比如一个res 结构体,这个该如何做啊

每个 goroutine 都想要要返回数据的话,errgroup 就不太合适了,可以在 errgroup 的基础上自己封装一下,这个也不麻烦

slzheng2017 commented 3 years ago

大哥,我有这样的问题,errgroup可以做到当一个goroutine出错时返回错误,但我还希望每个goroutine能够返回额外的数据,比如一个res 结构体,这个该如何做啊

每个 goroutine 都想要要返回数据的话,errgroup 就不太合适了,可以在 errgroup 的基础上自己封装一下,这个也不麻烦

我这几天又研究了下,我在errgroup.Go的匿名函数里,调用一个耗时的方法,并最终返回 err。但这种情况就用不了<-ctx.Done() 来控制:当一个goroutine出错时返回错误,其他goroutine不再执行,因为这些所谓的其他goroutine正在执行这个耗时的方法

        // errgroup:用于在goroutine并发过程失败时,终止其他goroutine,并返回第一个error
    group := new(errgroup.Group)
    // resp append 锁
    var mutex  sync.Mutex
    // TODO: 控制数量
    for i := 0; i < len(req.ins); i++ {
        index := i
        spec := req.ins[i]
        param1:= req.param1
        param2:= req.param2
        group.Go(func() error {
                        // innerfunc十分耗时
            ins, err := innerfunc(param0, param1, param2, index)
            if err == nil {
                mutex.Lock()
                resp.ins= append(resp.ins, &ins)
                mutex.Unlock()
            }
            return err
        })
    }
       if err := group.Wait(); err != nil {
           ...
       }

就像这个文章说的一样:https://www.5axxw.com/questions/content/xmjl08 我要如何做到当一个innerfunc出错时,其他goroutine的innerfunc可以中断返回,还是说没法做到

mohuishou commented 3 years ago

@slzheng2017 可以这么做, 当然实际上那个耗时的协程在没执行完的时候还是没有退出的,但是我猜测这样应该可以满足你的需求


func main() {
    g, ctx := errgroup.WithContext(context.Background())

    g.Go(func() error {
        result := make(chan string)

        go func() {
            fmt.Println("will sleep 3s")
            time.Sleep(3 * time.Second)
            fmt.Println("sleep 3s done")
            result <- "done"
        }()

        select {
        case <-ctx.Done():
            return fmt.Errorf("errgroup will exit")
        case r := <-result:
            fmt.Printf("res: %s\n", r)
            return nil
        }
    })

    g.Go(func() error {
        fmt.Println("will sleep 1s")
        time.Sleep(1 * time.Second)
        // 返回错误一秒后主程序就会推出
        return fmt.Errorf("err")
        // 不返回错误,另外一个 协程会执行完毕
        // return nil
    })

    fmt.Printf("done: %v", g.Wait())
}

这个其实涉及到了context的用法,可以参考这篇文章 https://lailin.xyz/post/go-training-week3-context.html#%E8%B6%85%E6%97%B6%E6%8E%A7%E5%88%B6

slzheng2017 commented 3 years ago

@slzheng2017 可以这么做, 当然实际上那个耗时的协程在没执行完的时候还是没有退出的,但是我猜测这样应该可以满足你的需求这个思路我也想到过了,但不要创建一个你不知道何时退出的 goroutine

slzheng2017 commented 3 years ago

这个思路我也想到过,但之前有看过你的文章:不要创建一个你不知道何时退出的 goroutine。所以就比较犹豫。一旦接收到ctx.Done()返回的信号,当前协程会退出,而那个耗时的协程就变成孤魂野鬼了,游荡中。。。

mohuishou commented 3 years ago

@slzheng2017 这个你要看具体的场景,目前看起来你这个只是在错误的情况下才会有不受控的 goroutine 并且这个地方看起来是一个请求,你可以给这个 goroutine 做好 recover,避免函数内 panic 导致整个服务挂掉,然后如果是请求的话设置好超时时间就行了

当然如果你这个经常出错,然后这个请求还会一直 hang 住另说

去看标准库的话你会发现一些类似的做法

slzheng2017 commented 3 years ago

感谢大神!

mohuishou commented 3 years ago

感谢大神!

客气了😝

slzheng2017 commented 3 years ago

大神,我又来了。我发现一个问题,使用errgroup+context后,性能有明显的下降。 我感觉通过管道来传递信号,性能影响很大 不使用errgroup和context:cost total time: 2925 ms 使用errgroup和context:cost total time: 4945 ms

mohuishou commented 3 years ago

@slzheng2017 这个要看你怎么用,channel 肯定比直接用锁性能低一些,它的实现就是用锁实现的,不过一般如果写业务开发,这点区别不是很大,优化这个还不如优化一下 sql 之类的见效快

wolf-joe commented 3 years ago

最后的案例中,g3里需要return ctx.Err()吗?直接return nil是不是更好?

mohuishou commented 3 years ago

最后的案例中,g3里需要return ctx.Err()吗?直接return nil是不是更好?

这个没太大关系,因为 context 退出说明之前有 Goroutine 退出了,调用了 cancel 方法

why444216978 commented 2 years ago

“只要一个 goroutine 出错我们就不再等其他 goroutine 了,减少资源浪费”,这个我看 errgroup 源码理解应该是没有解决这个问题吧。

rea1shane commented 2 years ago

“只要一个 goroutine 出错我们就不再等其他 goroutine 了,减少资源浪费”,这个我看 errgroup 源码理解应该是没有解决这个问题吧。

我也发现了这个问题,是需要自己实现取消么?如果是的话怎么优雅的实现取消所有正在运行与尚未运行的协程。

wolf-joe commented 2 years ago

“只要一个 goroutine 出错我们就不再等其他 goroutine 了,减少资源浪费”,这个我看 errgroup 源码理解应该是没有解决这个问题吧。

我也发现了这个问题,是需要自己实现取消么?如果是的话怎么优雅的实现取消所有正在运行与尚未运行的协程。

标准库/第三方库中涉及到io的库一般都会去主动判断ctx是否结束(case <-ctx.Done()),一般不需要自己实现取消