等待多个 goroutine 的结果

Posted

技术标签:

【中文标题】等待多个 goroutine 的结果【英文标题】:Wait result of multiple goroutines 【发布时间】:2018-04-05 00:31:48 【问题描述】:

我正在寻找一种在 go 中异步执行两个函数的方法,它们返回不同的结果和错误,等待它们完成并打印两个结果。此外,如果其中一个函数返回错误,我不想等待另一个函数,而只是打印错误。 例如,我有这个功能:

func methodInt(error bool) (int, error) 
    <-time.NewTimer(time.Millisecond * 100).C
    if error 
        return 0, errors.New("Some error")
     else 
        return 1, nil
    


func methodString(error bool) (string, error) 
    <-time.NewTimer(time.Millisecond * 120).C
    if error 
        return "", errors.New("Some error")
     else 
        return "Some result", nil
    

这里https://play.golang.org/p/-8StYapmlg 是我实现它的方式,但我认为它的代码太多了。它可以通过使用 interface 来简化,但我不想这样。我想要一些更简单的东西,例如,可以在 C# 中使用 async/await 实现。可能有一些库可以简化这种操作。

更新:感谢您的回复!我得到帮助的速度真是太棒了!我喜欢 WaitGroup 的用法。它显然使代码对更改更加健壮,因此我可以轻松地添加另一个异步方法,而最终无需更改方法的确切计数。但是,与 C# 中的相同代码相比,仍然有很多代码。我知道在 go 中我不需要将方法显式标记为异步,使它们实际上返回任务,但是方法调用看起来要简单得多,例如,考虑这个链接actually catching exception is also needed 顺便说一句,我发现在我的任务中我实际上不需要知道我想要运行异步的函数的返回类型,因为它无论如何都会被编组为 json,现在我只是在 go 的端点层调用多个服务-工具包。

【问题讨论】:

Close multiple goroutine if an error occurs in one in go 可能重复。 【参考方案1】:

您应该为错误和结果创建两个通道,然后如果没有错误则首先读取错误然后读取结果,此示例应该适用于您的用例:

package main

import (
    "errors"
    "sync"
)

func test(i int) (int, error) 
    if i > 2 
        return 0, errors.New("test error")
    
    return i + 5, nil


func test2(i int) (int, error) 
    if i > 3 
        return 0, errors.New("test2 error")
    
    return i + 7, nil


func main() 
    results := make(chan int, 2)
    errors := make(chan error, 2)
    var wg sync.WaitGroup
    wg.Add(1)
    go func() 
        defer wg.Done()
        result, err := test(3)
        if err != nil 
            errors <- err
            return
        
        results <- result
    ()
    wg.Add(1)
    go func() 
        defer wg.Done()
        result, err := test2(3)
        if err != nil 
            errors <- err
            return
        
        results <- result
    ()

    // here we wait in other goroutine to all jobs done and close the channels
    go func() 
        wg.Wait()
        close(results)
        close(errors)
    ()
    for err := range errors 
        // here error happend u could exit your caller function
        println(err.Error())
        return

    
    for res := range results 
        println("--------- ", res, " ------------")
    

【讨论】:

【参考方案2】:

我觉得这里可以使用sync.WaitGroup。它可以等待不同和动态数量的 goroutine。

【讨论】:

nathanleclaire.com/blog/2014/02/15/…sync.WaitGroup 上不错的博文和文档golang.org/pkg/sync/#WaitGroup 更新文章nathanleclaire.com/blog/2014/02/21/…【参考方案3】:

我创建了一个较小的、独立的示例,说明如何让两个 go 例程异步运行并等待两者完成或在发生错误时退出程序(请参阅下面的说明):

package main

import (
    "errors"
    "fmt"
    "math/rand"
    "time"
)

func main() 
    rand.Seed(time.Now().UnixNano())

    // buffer the channel so the async go routines can exit right after sending
    // their error
    status := make(chan error, 2)

    go func(c chan<- error) 
        if rand.Intn(2) == 0 
            c <- errors.New("func 1 error")
         else 
            fmt.Println("func 1 done")
            c <- nil
        
    (status)

    go func(c chan<- error) 
        if rand.Intn(2) == 0 
            c <- errors.New("func 2 error")
         else 
            fmt.Println("func 2 done")
            c <- nil
        
    (status)

    for i := 0; i < 2; i++ 
        if err := <-status; err != nil 
            fmt.Println("error encountered:", err)
            break
        
    

我所做的是创建一个用于同步两个 go 例程的通道。写入和读取它会阻塞。通道用于传递错误值,如果函数成功,则为 nil。

最后,我从通道中为每个异步 go 例程读取一个值。这会阻塞,直到收到一个值。如果发生错误,我退出循环,从而退出程序。

函数要么成功要么随机失败。

我希望这能让你了解如何协调 go 例程,如果没有,请在 cmets 中告诉我。

注意,如果你在 Go Playground 中运行它,rand.Seed 将什么都不做,playground 总是有相同的“随机”数字,所以行为不会改变。

【讨论】:

关闭(或缓冲)通道,否则会泄漏 goroutines(两个发送,但如果第一个值不是 nil 则只有一个接收;就像问题中的示例一样)。 实际上,在我的示例中,程序将终止主 go 例程,关闭运行时,因此没有任何泄漏。但你是对的,在更复杂的情况下,这可能是个问题。

以上是关于等待多个 goroutine 的结果的主要内容,如果未能解决你的问题,请参考以下文章

go内存泄露case

如何等待多个 goroutine 完成?

go goroutine id

Go 并发

如何关闭多个 goroutine 正在发送的通道?

如何在不使用 time.Sleep 的情况下等待所有 goroutines 完成?