英文:
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)
}
}
英文:
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 (
"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)
}
}
答案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 (
"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
}
答案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 (
"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)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论