英文:
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("defer done")
fmt.Println("num ", n)
if n == 1 {
ch <- fmt.Errorf("error")
} else {
ch <- nil
}
}
func main() {
var wg sync.WaitGroup
var err error
errs := make(chan error)
platforms := 2
types := 3
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)
}
}
wg.Wait()
err = <-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()
}
此外,你的代码中还有几个错误和不准确之处,我对其进行了重构:
- 你不应该在错误通道中写入nil。如果你希望
errs
通道只包含错误,那么只有在函数执行时出现非nil错误时才写入通道。 - 你在
j
循环的开头多写了一个wd.Add(1)
,导致Add
函数和Done
函数之间不平衡。 - 此外,你在
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 (->errs
) block execution flow until read operation (<-errs
) will be presented.
In your particular example you have unbuffered channel (the channel with 0 capacity). Thus any write operation (->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 = <-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("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()
}
In addition you have several bugs and inaccuracies that I refactored in your code:
- 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. - You had one extra wd.Add(1) as the beginning of
j
loop so there was disbalance betweenAdd
functions andDone
function. 3. - Furthemore, you add
defer fmt.Println("defer done")
afterdefer wg.Done()
butdefer
s constructions are executed in reversed order than they were specified so it would be more correct to putdefer fmt.Println("defer done")
beforedefer wg.Done()
so that "defer done" would really signalize that all previousdefer
s had been executed.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论