惯用的goroutine并发和错误处理

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

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 (
    	&quot;fmt&quot;
    	&quot;sync&quot;
    )
    
    func processBatch(num int, errChan chan&lt;- error, resultChan chan&lt;- int, wg *sync.WaitGroup) {
    	defer wg.Done()
    
    	if num == 3 {
    		resultChan &lt;- 0
    		errChan &lt;- fmt.Errorf(&quot;goroutine %d&#39;s error returned&quot;, num)
    	} else {
    		square := num * num
    
    		resultChan &lt;- square
    
    		errChan &lt;- 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, &amp;wg)
    	}
    
    	var results [5]int
    	var err [5]error
    	for i := range batches {
    		results[i] = &lt;-resultChan
    		err[i] = &lt;-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]
[&lt;nil&gt; &lt;nil&gt; &lt;nil&gt; goroutine 3&#39;s error returned &lt;nil&gt;]

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 (
	&quot;fmt&quot;
)

func processBatch(num int, errChan chan&lt;- error, resultChan chan&lt;- int) {
	if num == 3 {
		// no need to send result when it is meanenless
		// resultChan &lt;- 0
		errChan &lt;- fmt.Errorf(&quot;goroutine %d&#39;s error returned&quot;, num)
	} else {
		square := num * num

		resultChan &lt;- square

		// no need to send errror when it is nil
		// errChan &lt;- 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 := &lt;-resultChan:
			results = append(results, res)
		case err := &lt;-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&#39;s error returned]

If you can use external packages, we can get benefits from some multierr package. For example, github.com/hashicorp/go-multierror.

huangapple
  • 本文由 发表于 2022年6月16日 20:40:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/72646034.html
匿名

发表评论

匿名网友

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

确定