英文:
Idiomatic goroutine concurrency and error handling
问题
在下面的代码块中,我尝试运行几个例程,并获取它们的结果(成功或错误)。
package main
import (
"fmt"
"sync"
)
func processBatch(num int, errChan chan<- error, resultChan chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
if num == 3 {
resultChan <- 0
errChan <- fmt.Errorf("goroutine %d's error returned", num)
} else {
square := num * num
resultChan <- square
errChan <- nil
}
}
func main() {
var wg sync.WaitGroup
batches := [5]int{1, 2, 3, 4, 5}
resultChan := make(chan int)
errChan := make(chan error)
for i := range batches {
wg.Add(1)
go processBatch(batches[i], errChan, resultChan, &wg)
}
var results [5]int
var err [5]error
for i := range batches {
results[i] = <-resultChan
err[i] = <-errChan
}
wg.Wait()
close(resultChan)
close(errChan)
fmt.Println(results)
fmt.Println(err)
}
这段代码可以正常工作,并且我得到了想要的结果:
[25 1 4 0 16]
[<nil> <nil> <nil> goroutine 3's error returned <nil>]
我想知道是否有更符合惯用方式的方法来实现这个。我查看了errgroup
包(https://pkg.go.dev/golang.org/x/sync/errgroup),但没有找到可以帮助我的内容。欢迎提出任何建议。
英文:
In the below code block I am trying to run several routines and get results (Whether success or error) for all of them.
package main
import (
"fmt"
"sync"
)
func processBatch(num int, errChan chan<- error, resultChan chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
if num == 3 {
resultChan <- 0
errChan <- fmt.Errorf("goroutine %d's error returned", num)
} else {
square := num * num
resultChan <- square
errChan <- nil
}
}
func main() {
var wg sync.WaitGroup
batches := [5]int{1, 2, 3, 4, 5}
resultChan := make(chan int)
errChan := make(chan error)
for i := range batches {
wg.Add(1)
go processBatch(batches[i], errChan, resultChan, &wg)
}
var results [5]int
var err [5]error
for i := range batches {
results[i] = <-resultChan
err[i] = <-errChan
}
wg.Wait()
close(resultChan)
close(errChan)
fmt.Println(results)
fmt.Println(err)
}
Playground: https://go.dev/play/p/zA-Py9gDjce
This code works and I get the result that I want i.e:
[25 1 4 0 16]
[<nil> <nil> <nil> goroutine 3's error returned <nil>]
I was wondering if there is a more idiomatic way to achieve this. I went through the errgroup package: https://pkg.go.dev/golang.org/x/sync/errgroup but wasn't able to find something that may help me here. Any suggestions are welcome.
答案1
得分: 2
Waitgroup在这段代码中是多余的。执行与等待通道结果的循环完全同步。直到所有函数完成工作并从通道中读取到结果,代码才会继续执行。
只有在函数需要在结果发布到通道后执行任何工作时,Waitgroup才是必需的。
我更喜欢稍微不同的实现方式。在发布的实现中,每次执行函数时我们不需要同时发送结果和错误到通道中。相反,我们可以只在执行成功时发送结果,而在代码失败时只发送错误。
这样做的好处是简化了结果/错误处理。我们可以获取到不带nil
的结果和错误片段。
在这个示例中,函数返回一个数字,在出现错误时我们发送其默认值0
。如果零值可能是合法的函数执行结果,那么从成功的执行结果中过滤出不成功的执行结果可能会变得复杂。
错误也是一样的。我们可以使用简单的代码if len(errs) != 0
来检查是否有任何错误。
如果你可以使用外部包,我们可以从一些multierr
包中获得好处。例如,github.com/hashicorp/go-multierror
。
package main
import (
"fmt"
"github.com/hashicorp/go-multierror"
)
func processBatch(num int) (int, error) {
if num == 3 {
return 0, fmt.Errorf("goroutine %d's error returned", num)
} else {
square := num * num
return square, nil
}
}
func main() {
batches := [5]int{1, 2, 3, 4, 5}
var resultErr error
var results []int
for _, num := range batches {
res, err := processBatch(num)
if err != nil {
resultErr = multierror.Append(resultErr, err)
} else {
results = append(results, res)
}
}
fmt.Println(results)
fmt.Println(resultErr)
}
这样的实现方式更简洁,并且使用了multierror
包来处理错误。
英文:
Waitgroup is redundant in this code. Execution is perfectly synced with the loop that is waiting for the channel's result. Code is not moved forward until all functions finish their work and posted results are read from the channels.
Waitgroup is only necessary if your function needs to do any work AFTER results are posted to channels.
I also prefer a slightly different implementation. In a posted implementation, we are not sending both results and errors into the channels every time when the function is executed. Instead, we can send only the result for successful execution and send only an error when the code fails.
The advantage is simplified results/errors processing. We are getting slices of results and errors without nils
.
In this example, the function returns a number, and we send its default value of 0
in case of error. It could be complicated to filter out not successful execution results from successful if zero could be legit function execution result.
Same with errors. To check if we have any errors, we can use simple code like if len(errs) != 0
.
package main
import (
"fmt"
)
func processBatch(num int, errChan chan<- error, resultChan chan<- int) {
if num == 3 {
// no need to send result when it is meanenless
// resultChan <- 0
errChan <- fmt.Errorf("goroutine %d's error returned", num)
} else {
square := num * num
resultChan <- square
// no need to send errror when it is nil
// errChan <- nil
}
}
func main() {
batches := [5]int{1, 2, 3, 4, 5}
resultChan := make(chan int)
errChan := make(chan error)
for i := range batches {
go processBatch(batches[i], errChan, resultChan)
}
// use slices instead of arrays because legth varry now
var results []int
var errs []error
// every time function executes it sends singe piece of data to one of two channels
for range batches {
select {
case res := <-resultChan:
results = append(results, res)
case err := <-errChan:
errs = append(errs, err)
}
}
close(resultChan)
close(errChan)
fmt.Println(results)
fmt.Println(errs)
}
https://go.dev/play/p/SYmfl8iGxgD
[25 1 16 4]
[goroutine 3's error returned]
If you can use external packages, we can get benefits from some multierr
package. For example, github.com/hashicorp/go-multierror
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论