Go协程突然结束,通道在未达到关闭语句的情况下关闭。

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

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 &lt;-chan int) &lt;-chan int {
out := make(chan int)
go func() {
for num := range in {
fmt.Printf(&quot;Receving num %v\n&quot;, num)
out &lt;- num * num
fmt.Printf(&quot;Sending square %v\n&quot;, num * num)
}
fmt.Println(&quot;Closing out&quot;)
close(out)
}()
return out
}
func fanOut(in &lt;-chan int, workerCount int) []&lt;-chan int {
outs := make([]&lt;-chan int, 0, workerCount)
for i := 0 ; i &lt; workerCount ; i++ {
outs = append(outs, calculateSquare(in))
}
return outs
}
func fanIn(outs []&lt;-chan int) &lt;-chan int {
var wg sync.WaitGroup
merge := make(chan int)
for _, out := range outs {
wg.Add(1)
go func() {
for result := range out {
merge &lt;- result
}
wg.Done()
}()
}
go func() {
wg.Wait()
fmt.Println(&quot;Closing merge&quot;)
close(merge)
}()
return merge
}
func main() {
in := make(chan int)
go func() {
for i := 0 ; i &lt; 4 ; i++ {
fmt.Printf(&quot;Sending num %v\n&quot;, i)
in &lt;- i
}
close(in)
}()
outs := fanOut(in, 5)
merge := fanIn(outs)
for num := range merge {
fmt.Printf(&quot;Final square %v\n&quot;, 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 // &lt;- 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 &lt;- 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 &lt;- chan int) {
for result := range c {
merge &lt;- result
}
wg.Done()
}(out)
}

huangapple
  • 本文由 发表于 2021年8月6日 21:52:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/68682768.html
匿名

发表评论

匿名网友

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

确定