英文:
Go routine ending mysteriously, channel closed without reaching close statement
问题
我创建了以下简单的程序来测试使用通道的扇入扇出模式。它的功能是生成几个goroutine来计算从输入通道接收的数字的平方,并将平方发送到输出通道。然后,所有输出通道将合并到一个单一的通道中,以便在main
函数中打印平方。
func calculateSquare(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for num := range in {
fmt.Printf("Receving num %v\n", num)
out <- num * num
fmt.Printf("Sending square %v\n", num * num)
}
fmt.Println("Closing out")
close(out)
}()
return out
}
func fanOut(in <-chan int, workerCount int) []<-chan int {
outs := make([]<-chan int, 0, workerCount)
for i := 0 ; i < workerCount ; i++ {
outs = append(outs, calculateSquare(in))
}
return outs
}
func fanIn(outs []<-chan int) <-chan int {
var wg sync.WaitGroup
merge := make(chan int)
for _, out := range outs {
wg.Add(1)
go func() {
for result := range out {
merge <- result
}
wg.Done()
}()
}
go func() {
wg.Wait()
fmt.Println("Closing merge")
close(merge)
}()
return merge
}
func main() {
in := make(chan int)
go func() {
for i := 0 ; i < 4 ; i++ {
fmt.Printf("Sending num %v\n", i)
in <- i
}
close(in)
}()
outs := fanOut(in, 5)
merge := fanIn(outs)
for num := range merge {
fmt.Printf("Final square %v\n", num)
}
}
在main
函数中,我将数字0到3发送到输入通道,并期望在控制台上看到4个平方数。然而,当我运行程序时,尽管输出有些波动,但我从来没有在控制台上看到4个平方数被打印出来。
下面是我看到的一个示例输出。
Sending num 0
Sending num 1
Sending num 2
Sending num 3
Closing out
Receving num 0
Receving num 1
Receving num 2
Sending square 4
Closing out
Receving num 3
Final square 4
Closing merge
如果有人能解释一下为什么打印了Receving num 1
,但是Sending square 1
从未出现,我将非常感激。此外,如果Sending square 1
没有被打印,那么output
通道是如何关闭的呢?我只看到了2个Closing out
,然而,我合并结果的等待组已经结束了Wait()
。
我一定在某个地方做错了什么。
英文:
I created the following simple program to test the fan-in-fan-out pattern using channel. What it does is generate a few go routines to calculate the square of a number coming from an input channel and send the square into an output channel. All output channels will then be merged into a single channel to print the square in main
.
func calculateSquare(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for num := range in {
fmt.Printf("Receving num %v\n", num)
out <- num * num
fmt.Printf("Sending square %v\n", num * num)
}
fmt.Println("Closing out")
close(out)
}()
return out
}
func fanOut(in <-chan int, workerCount int) []<-chan int {
outs := make([]<-chan int, 0, workerCount)
for i := 0 ; i < workerCount ; i++ {
outs = append(outs, calculateSquare(in))
}
return outs
}
func fanIn(outs []<-chan int) <-chan int {
var wg sync.WaitGroup
merge := make(chan int)
for _, out := range outs {
wg.Add(1)
go func() {
for result := range out {
merge <- result
}
wg.Done()
}()
}
go func() {
wg.Wait()
fmt.Println("Closing merge")
close(merge)
}()
return merge
}
func main() {
in := make(chan int)
go func() {
for i := 0 ; i < 4 ; i++ {
fmt.Printf("Sending num %v\n", i)
in <- i
}
close(in)
}()
outs := fanOut(in, 5)
merge := fanIn(outs)
for num := range merge {
fmt.Printf("Final square %v\n", num)
}
}
In the main
function, I'm sending in 4 numbers 0 -> 3 into the input channel and I expect to see 4 square printed in the console. However, when I ran the program, even though the output fluctuates a bit but I never ever see 4 square numbers printed in the console.
Below is a sample output I'm seeing.
Sending num 0
Sending num 1
Sending num 2
Sending num 3
Closing out
Receving num 0
Receving num 1
Receving num 2
Sending square 4
Closing out
Receving num 3
Final square 4
Closing merge
I'd be very grateful if someone could explain to me why Receving num 1
was printed but Sending square 1
is never coming. In addition, if Sending square 1
is not printed, how did the output
channel get closed. I'm only seeing 2 Closing out
, yet, the wait group where I was merging the result ended its Wait()
.
I must have done something wrong somewhere.
答案1
得分: 3
修复:
for _, out := range outs {
wg.Add(1)
out := out // <- 添加这行代码
}
为什么?
https://golang.org/doc/effective_go 是一个很好的资源,它在 channels section 的最后详细介绍了闭包 bug(@JimB 提到的):
写下面的代码可能看起来有点奇怪:
req := req
但在 Go 语言中,这是合法且惯用的写法。你会得到一个同名的新变量,意味着在每个 goroutine 中都有一个局部的循环变量的阴影版本,但是每个版本都是唯一的。
英文:
To fix:
for _, out := range outs {
wg.Add(1)
out := out // <- add this
Why?
https://golang.org/doc/effective_go is an excellent resource and covers the exact closure bug (that @JimB mentioned) towards the end of the channels section:
> It may seem odd to write
>
> req := req
>
> but it's legal and idiomatic in Go to do this. You get a
> fresh version of the variable with the same name, deliberately
> shadowing the loop variable locally but unique to each goroutine.
答案2
得分: 2
你的问题出在fanIn
函数的for循环中。
问题出在你在gofunc中使用了out
迭代变量,当gofunc要使用它时,循环已经结束了。
这在go/wiki/CommonMistakes的"Using goroutines on loop iterator variables"子主题下有描述。
更多示例请阅读这里。
修正后的循环应该如下所示:
for _, out := range outs {
wg.Add(1)
go func(c <- chan int) {
for result := range c {
merge <- result
}
wg.Done()
}(out)
}
英文:
your issue is in the code below, for loop in fanIn
function.
for _, out := range outs {
wg.Add(1)
go func() {
for result := range out {
merge <- result
}
wg.Done()
}()
}
Reason for this is you using out
iterator variable in gofunc, when gofunc going to use it, loop is gone to it's end.
This is describe in go/wiki/CommonMistakes under the sub topic Using goroutines on loop iterator variables
For more example - read this
corrected loop should be as below,
for _, out := range outs {
wg.Add(1)
go func(c <- chan int) {
for result := range c {
merge <- result
}
wg.Done()
}(out)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论