如何尽早 return 来自循环内 gouroutine 的错误?

How to return the error from the gouroutine inside a loop early?

我在一个循环中有一个 goroutine,我处理错误的方式是将它添加到一个通道中,在所有 goroutine 完成后,我检查是否有错误,然后我 return因此。

这个问题是我想 return 一旦我得到一个错误,这样我就不会花时间等待所有的 goroutines 完成,因为它是低效的。

我尝试添加 select 语句但它不起作用并且我无法在 goroutines 中添加 select 语句因为我想退出 for 循环和 try 也有功能。

我该怎么做?

代码如下:

package main

import (
    "sync"
    "runtime"
    "fmt"
    "errors"
)

func try() (bool, error) {
    wg := new(sync.WaitGroup)

    s := []int{0,1,2,3,4,5}
    ec := make(chan error)
    
    for i, val := range s {
    /*
        select {
             case err, ok := <-ec:
        if ok {
            println("error 1", err.Error())
            return false, err
        }
            default:
            }
    */
        wg.Add(1)
        i := i
        val := val
        go func() {
            err := func(i int, val int, wg *sync.WaitGroup) error {
                defer wg.Done()
                
                if i == 3 {
                    return errors.New("one error")
                } else {
                    return nil
                }
                
            }(i, val, wg)
            if err != nil {
                ec <- err
                return
            }
        }()
    }
    wg.Wait()
    
    select {
    case err, ok := <-ec:
        if ok {
            println("error 2", err.Error())
            return false, err
        }
    default:
    }
    
    return true, nil
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    b, e := try()
    if e != nil {
        fmt.Println(e.Error(), b)
    } 
    
}

This is the go playground link

我发现 tomb 对此很有用。下面是一个精简的非工作示例,它显示了要点,没有处理循环中的变量封装之类的事情。它应该给你想法,但我很乐意澄清任何一点。

package main

import (
    "fmt"
    "gopkg.in/tomb.v2"
    "sync"
)

func main() {
    ts := tomb.Tomb{}
    s := []int{0,1,2,3,4,5}

    for i, v := range s {
        ts.Go(func() error {
            // do some work here or return an error, make sure to watch the dying chan, if it closes, 
            //then one of the other go-routines failed.
            select {
            case <- ts.Dying():
                return nil
            case err := <- waitingForWork():
                if err != nil {
                    return err
                }
                return nil
            }
        })
    }

    // If an error appears here, one of the go-routines must have failed
    err := ts.Wait()
    if err != nil {
        fmt.Println(err)
    }
}

select 语句之前使用 wg.Wait(),您实际上是在等待所有 goroutines return.

The issue with this is that I want to return an error as soon as I get it

我假设你的意思是一旦其中任何一个 return 出现错误就停止 运行 goroutines。

在这种情况下,您可以使用 context.Context 来管理取消,但更好的是 errgroup.Group,它很好地结合了上下文功能和同步:

Package errgroup provides synchronization, error propagation, and Context cancelation for groups of goroutines working on subtasks of a common task.

特别是Group.Go:

The first call to return a non-nil error cancels the group; its error will be returned by Wait.

import (
    "sync"
    "runtime"
    "fmt"
    "errors"
    "golang.org/x/sync/errgroup"
)

func try() (bool, error) {
    errg := new(errgroup.Group)

    s := []int{0,1,2,3,4,5}
    
    for i, val := range s {       
        i := i
        val := val

        errg.Go(func() error {
            return func(i int, val int) error {
                if i == 3 {
                    return errors.New("one error")
                } else {
                    return nil
                }
            }(i, val)
        })
    }
    
    if err := errg.Wait(); err != nil {
        // handle error
    }
    
    return true, nil
}

https://play.golang.org/p/lSIIFJqXf0W