英文:
Is errgroup in golang restrictive in its usage?
问题
我正在学习golang,对于在使用context时与errgroup包混淆不清。这是我的简单代码:
package main
import (
"context"
"errors"
"fmt"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
fmt.Println("..................")
ctx := context.Background()
group, ctx := errgroup.WithContext(ctx)
ctx, cancel := context.WithCancel(ctx)
group.Go(func() error {
//return errors.New("Error 1")
time.Sleep(8 * time.Second)
fmt.Println("Sleep 1 ended..................")
cancel()
return errors.New("Error 1")
})
group.Go(func() error {
//return errors.New("Error 1")
time.Sleep(2 * time.Second)
fmt.Println("Sleep 2 ended..................")
cancel()
return errors.New("Error 2")
})
err := group.Wait()
if err != nil {
fmt.Println("Error: ", err)
}
fmt.Println("..................")
}
预期输出为:
..................
Sleep 2 ended..................
Sleep 1 ended..................
Error: Error 2
..................
group.Wait()
"阻塞直到Go方法中的所有函数调用都返回,然后从它们中返回第一个非nil的错误(如果有的话)"。
问题:
- 如果我想使用errgroup,但希望等待所有Go方法共享的上下文被取消或所有Go方法的函数调用都返回,该怎么办?
- 如果我想使用errgroup,但希望等待其中一个Go方法返回错误,哪个方法会取消上下文并不等待所有方法完成?
我有些感觉errgroup包在使用上有些限制。我漏掉了什么吗?
英文:
I am learning golang and am confused about errgroup package when used with context.
Here is my simple code:
package main
import (
"context"
"errors"
"fmt"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
fmt.Println("..................")
ctx := context.Background()
group, ctx := errgroup.WithContext(ctx)
ctx, cancel := context.WithCancel(ctx)
group.Go(func() error {
//return errors.New("Error 1")
time.Sleep(8 * time.Second)
fmt.Println("Sleep 1 ended..................")
cancel()
return errors.New("Error 1")
})
group.Go(func() error {
//return errors.New("Error 1")
time.Sleep(2 * time.Second)
fmt.Println("Sleep 2 ended..................")
cancel()
return errors.New("Error 2")
})
err := group.Wait()
if err != nil {
fmt.Println("Error: ", err)
}
fmt.Println("..................")
}
The output, as expected is:
..................
Sleep 2 ended..................
Sleep 1 ended..................
Error: Error 2
..................
group.Wait() "blocks until all function calls from the Go method have returned, then returns the first non-nil error (if any) from them."
Questions:
- What if I want to use errgroup but want to wait until the
context shared by all the Go methods is cancelled or all
function calls from the Go method have returned? - What if I want to use errgroup but want to wait until one of the Go method has returned error, which method will cancel the context and not wait for all to finish?
Somehow I feel that errgroup package is too restrictive in its use. What am I missing?
答案1
得分: 1
似乎仅仅使用errGroup
本身无法实现这个目标。
- 可能可以在这里使用
waitGroup
。 - 可能只在发生错误时调用
cancel
。或者使用error
通道,并等待第一个错误。
英文:
It seems like this cannot be achieved just by using errGroup
itself.
- Maybe
waitGroup
can be used here. - Maybe call
cancel
only if error happened. Or useerror
channel and wait till first error.
答案2
得分: 1
这个实现回答了你的两个问题:
package main
import (
"context"
"errors"
"fmt"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
ctx := context.Background()
g, gtx := errgroup.WithContext(ctx)
g.Go(func() error {
select {
case <-time.After(8 * time.Second):
fmt.Println("Sleep 1 ended..................")
return errors.New("Error 1")
case <-gtx.Done():
return gtx.Err()
}
})
g.Go(func() error {
select {
case <-time.After(2 * time.Second):
fmt.Println("Sleep 2 ended..................")
return errors.New("Error 2")
case <-gtx.Done():
return gtx.Err()
}
})
err := g.Wait()
if err != nil {
fmt.Println("Error: ", err)
}
fmt.Println("..................")
}
它将打印以下内容:
..................
Sleep 2 ended..................
Error: Error 2
..................
返回错误会隐式取消上下文。你需要等待 ctx.Done()
显式地放弃执行 goroutine。
使用 time.Sleep
可能被认为是一种反模式,因为它不会响应上下文的取消。在许多情况下,你应该使用:
select {
case <-time.After(x):
case <-ctx.Done():
return ctx.Err()
}
英文:
This implementation answers both of your questions:
package main
import (
"context"
"errors"
"fmt"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
ctx := context.Background()
g, gtx := errgroup.WithContext(ctx)
g.Go(func() error {
select {
case <-time.After(8 * time.Second):
fmt.Println("Sleep 1 ended..................")
return errors.New("Error 1")
case <-gtx.Done():
return gtx.Err()
}
})
g.Go(func() error {
select {
case <-time.After(2 * time.Second):
fmt.Println("Sleep 2 ended..................")
return errors.New("Error 2")
case <-gtx.Done():
return gtx.Err()
}
})
err := g.Wait()
if err != nil {
fmt.Println("Error: ", err)
}
fmt.Println("..................")
}
It will print the following:
..................
Sleep 2 ended..................
Error: Error 2
..................
Returning an error cancels the context implicitly. You need to wait for ctx.Done()
to abandon the execution of the go routines explicitly.
Using time.Sleep
might be considered as an anti-pattern, as it doesn't acknowledge context cancelation. In many cases you want to use:
select {
case <-time.After(x):
case <-ctx.Done():
return ctx.Err()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论