如何在循环中提前返回 goroutine 的错误?

huangapple go评论89阅读模式
英文:

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

问题

我在循环中有一个goroutine,处理错误的方式是将其添加到一个通道中,在所有的goroutine完成后,检查是否有错误,并相应地返回。

这种方法的问题是,我希望一旦出现错误就立即返回,而不是等待所有的goroutine完成,因为这样效率很低。

我尝试添加select语句,但它不起作用,我也不能在goroutine中添加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)
	} 
	
}

这是Go Playground的链接

英文:

I have a goroutine inside a loop and the way I am handling the error is that I add it to a channel and after all the goroutines are finished, I check if there was an error and I return accordingly.

The issue with this is that I want to return an error as soon as I get it so that I don't spend time waiting for all the goroutines to finish as it would be inefficient.

I tried adding the select statement but it doesn't work and I can't add the select statement inside the goroutines since I want to exit the for loop and the try function too.

How can I do this?

Here is the code:

package main
import (
&quot;sync&quot;
&quot;runtime&quot;
&quot;fmt&quot;
&quot;errors&quot;
)
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 := &lt;-ec:
if ok {
println(&quot;error 1&quot;, 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(&quot;one error&quot;)
} else {
return nil
}
}(i, val, wg)
if err != nil {
ec &lt;- err
return
}
}()
}
wg.Wait()
select {
case err, ok := &lt;-ec:
if ok {
println(&quot;error 2&quot;, 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

答案1

得分: 2

使用wg.Wait()select语句之前,你实际上是在等待所有的goroutine返回。

这样做的问题是,我希望一旦出现错误就立即返回错误。

我理解你的意思是,一旦任何一个goroutine返回错误,就停止运行其他的goroutine。

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

errgroup包为一组在共同任务的子任务上工作的goroutine提供了同步、错误传播和上下文取消的功能。

特别是Group.Go方法:

第一个返回非nil错误的调用将取消该组;其错误将由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 {
        // 处理错误
    }
    
    return true, nil
}

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

英文:

With wg.Wait() before your select statement, you are effectively waiting for all goroutines to return.

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

I assume that with this you mean stopping running goroutines as soon as any one of them returns an error.

In this case, you could use context.Context to manage cancellation, but even better is an errgroup.Group, which nicely combines context functionality and synchronization:

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

In particular Group.Go:

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

import (
&quot;sync&quot;
&quot;runtime&quot;
&quot;fmt&quot;
&quot;errors&quot;
&quot;golang.org/x/sync/errgroup&quot;
)
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(&quot;one error&quot;)
} else {
return nil
}
}(i, val)
})
}
if err := errg.Wait(); err != nil {
// handle error
}
return true, nil
}

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

答案2

得分: 1

我发现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 {
			// 在这里进行一些工作或返回一个错误,确保观察 dying 通道,如果它关闭了,
			// 那么其他的 Go 协程中的一个肯定失败了。
			select {
			case <- ts.Dying():
				return nil
			case err := <- waitingForWork():
				if err != nil {
					return err
				}
				return nil
			}
		})
	}

	// 如果这里出现错误,那么肯定有一个 Go 协程失败了
	err := ts.Wait()
	if err != nil {
		fmt.Println(err)
	}
}

希望对你有帮助!

英文:

I have found tomb to be useful for this. Below is a stripped-down non-working example that shows the gist, without handling things like variable encapsulation in the loop. It should give you the idea, but I'm happy to clarify on any points.

package main
import (
&quot;fmt&quot;
&quot;gopkg.in/tomb.v2&quot;
&quot;sync&quot;
)
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 &lt;- ts.Dying():
return nil
case err := &lt;- 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)
}
}

huangapple
  • 本文由 发表于 2021年6月9日 00:10:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/67890717.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定