从嵌套在循环中的 goroutine 中收集错误。

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

Collect errors from goroutines nested in loops

问题

我正在尝试在循环中收集goroutine的错误,但是不太明白应该如何正确地工作。

您可以使用一个切片来收集错误,并在等待组完成后进行处理。以下是修改后的代码示例:

func init() {
    rand.Seed(1500929006430687579)
}

func goroutine(n int, wg *sync.WaitGroup, ch chan error) {
    defer wg.Done()
    defer fmt.Println("defer done")

    fmt.Println("num ", n)
    if n == 1 {
        ch <- fmt.Errorf("error")
    } else {
        ch <- nil
    }
}

func main() {
    var wg sync.WaitGroup
    errs := make(chan error)
    platforms := 2
    types := 3
    var errors []error // 创建一个切片来存储错误

    for j := 0; j < platforms; j++ {
        wg.Add(1)
        for k := 0; k < types; k++ {
            wg.Add(1)
            n := rand.Intn(2)
            go goroutine(n, &wg, errs)
        }

        for k := 0; k < types; k++ {
            wg.Add(1)
            n := rand.Intn(2)
            go goroutine(n, &wg, errs)
        }
    }

    go func() {
        wg.Wait()
        close(errs) // 关闭通道,表示不再有新的错误产生
    }()

    for err := range errs {
        if err != nil {
            errors = append(errors, err) // 将错误添加到切片中
        }
    }

    fmt.Println(errors) // 处理所有的错误
}

在修改后的代码中,我们创建了一个切片errors来存储错误。在每个goroutine中,如果有错误发生,我们将其发送到errs通道中。在主函数中,我们使用range循环来迭代通道中的错误,并将非空错误添加到切片中。最后,我们打印出所有的错误。

请注意,我们还添加了一个匿名的goroutine,用于在等待组完成后关闭errs通道。这样做是为了确保在所有goroutine完成后,range循环能够正常退出。

希望对你有帮助!

英文:

I'm trying to collect errors from goroutines in loop, but dont't understand how it must correctly work
https://go.dev/play/p/WrxE0vH6JSG

func init() {
rand.Seed(1500929006430687579)
}
func goroutine(n int, wg *sync.WaitGroup, ch chan error) {
defer wg.Done()
defer fmt.Println(&quot;defer done&quot;)
fmt.Println(&quot;num &quot;, n)
if n == 1 {
ch &lt;- fmt.Errorf(&quot;error&quot;)
} else {
ch &lt;- nil
}
}
func main() {
var wg sync.WaitGroup
var err error
errs := make(chan error)
platforms := 2
types := 3
for j := 0; j &lt; platforms; j++ {
wg.Add(1)
for k := 0; k &lt; types; k++ {
wg.Add(1)
n := rand.Intn(2)
go goroutine(n, &amp;wg, errs)
}
for k := 0; k &lt; types; k++ {
wg.Add(1)
n := rand.Intn(2)
go goroutine(n, &amp;wg, errs)
}
}
wg.Wait()
err = &lt;-errs
fmt.Println(err)
}

how should I collect array of errors correctly and done all wait groups?

答案1

得分: 1

在Golang中,通道(channels)类似于Bash中的管道(|)。但与Bash管道不同,Bash管道用于将一个命令的输出传输到另一个命令的输入,Go通道用于在goroutine之间传输数据。你可以在这里了解更多关于通道的信息。

通道有容量。当你不为通道指定容量时,Go会假设它的容量为0。容量为0的通道通常被称为“无缓冲通道”,而容量非零的通道被称为“有缓冲通道”。当通道已满(通道中的元素数量等于通道的容量)时,所有对通道的写操作(->errs)都会阻塞执行流,直到读操作(<-errs)出现为止。

在你的特定示例中,你有一个无缓冲通道(容量为0的通道)。因此,对通道的任何写操作(->errs)都会阻塞执行,直到提供了某个读操作,因此尽管你启动了多个goroutine,但所有的goroutine都会被阻塞,直到main函数的流程前进到读操作(err = <-errs)。

为了解决这个问题,你可以创建一个额外的goroutine,与写入通道的goroutine并发地从通道中读取。代码如下所示:

func init() {
	rand.Seed(1500929006430687579)
}

func goroutine(n int, wg *sync.WaitGroup, ch chan error) {
	defer fmt.Println("defer done")
	defer wg.Done()

	fmt.Println("num ", n)
	if n == 1 {
		ch <- fmt.Errorf("error")
	}
}

func main() {
	var wg sync.WaitGroup
	errs := make(chan error)
	platforms := 2
	types := 3

	go func() {
		for e := range errs {
			fmt.Println(e)
		}
	}()

	for j := 0; j < platforms; j++ {
		for k := 0; k < types; k++ {
			wg.Add(1)
			n := rand.Intn(2)
			go goroutine(n, &wg, errs)
		}

		for k := 0; k < types; k++ {
			wg.Add(1)
			n := rand.Intn(2)
			go goroutine(n, &wg, errs)
		}
	}
	wg.Wait()
}

此外,你的代码中还有几个错误和不准确之处,我对其进行了重构:

  1. 你不应该在错误通道中写入nil。如果你希望errs通道只包含错误,那么只有在函数执行时出现非nil错误时才写入通道。
  2. 你在j循环的开头多写了一个wd.Add(1),导致Add函数和Done函数之间不平衡。
  3. 此外,你在defer wg.Done()之后添加了defer fmt.Println("defer done"),但是defer语句的执行顺序与它们指定的顺序相反,所以更正确的做法是将defer fmt.Println("defer done")放在defer wg.Done()之前,这样"defer done"才能真正表示所有先前的defer已经执行完毕。
英文:

In Golang channels is similar to pipes in bash (|). But in contrast to bash pipes which are used to transport output of one command to input of another command, Go channels are used to transport some data between goroutines. You can read more about channels here.
Channels have capacity. When you don't specify capacity for the channel go assumes that it has 0 capacity. Channels with zero capacity often called unbuffered channels while channels with non-zero capacity called buffered. When channel is full (number of elements in channel is equal to channel's capacity) than all write operations on the channel (-&gt;errs) block execution flow until read operation (&lt;-errs) will be presented.

In your particular example you have unbuffered channel (the channel with 0 capacity). Thus any write operation (-&gt;errs) on your channel will block the execution until some read operation would be provided, therefore all goroutines that you launched will be blocked despite the only one goroutine that would be able to proceed write operation when the flow of the main function moved forward to read operation (err = &lt;-errs).

To solve your issue you could create one extra goroutine that would read from channel concurrently with goroutines that would write to channel. It will look like that:

func init() {
rand.Seed(1500929006430687579)
}
func goroutine(n int, wg *sync.WaitGroup, ch chan error) {
defer fmt.Println(&quot;defer done&quot;)
defer wg.Done()
fmt.Println(&quot;num &quot;, n)
if n == 1 {
ch &lt;- fmt.Errorf(&quot;error&quot;)
}
}
func main() {
var wg sync.WaitGroup
errs := make(chan error)
platforms := 2
types := 3
go func() {
for e := range errs {
fmt.Println(e)
}
}()
for j := 0; j &lt; platforms; j++ {
for k := 0; k &lt; types; k++ {
wg.Add(1)
n := rand.Intn(2)
go goroutine(n, &amp;wg, errs)
}
for k := 0; k &lt; types; k++ {
wg.Add(1)
n := rand.Intn(2)
go goroutine(n, &amp;wg, errs)
}
}
wg.Wait()
}

In addition you have several bugs and inaccuracies that I refactored in your code:

  1. You shouldn't write nil in channel with errors. If you want errs chan to comprise only errors so write there only if your function executed with non-nil error.
  2. You had one extra wd.Add(1) as the beginning of j loop so there was disbalance between Add functions and Done function. 3.
  3. Furthemore, you add defer fmt.Println(&quot;defer done&quot;) after defer wg.Done() but defers constructions are executed in reversed order than they were specified so it would be more correct to put defer fmt.Println(&quot;defer done&quot;) before defer wg.Done() so that "defer done" would really signalize that all previous defers had been executed.

huangapple
  • 本文由 发表于 2022年11月12日 03:24:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/74407256.html
匿名

发表评论

匿名网友

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

确定