go routine 神秘结束,channel 没有到达 close 语句就关闭了

Go routine ending mysteriously, channel closed without reaching close statement

我创建了以下简单程序来测试使用通道的扇入扇出模式。它所做的是生成一些 go 例程来计算来自输入通道的数字的平方并将平方发送到输出通道。然后所有输出通道将合并为一个通道以打印 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 函数中,我将 4 个数字 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 1Sending square 1 永远不会出现,我将不胜感激。另外,如果不打印Sending square 1output通道是怎么关闭的。我只看到 2 Closing out,但是,我合并结果的等待组结束了它的 Wait()

一定是我哪里做错了。

您的问题出在下面的代码中,fanIn 函数中的 for 循环。

    for _, out := range outs {
        wg.Add(1)

        go func() {
            for result := range out {
                merge <- result
            }

            wg.Done()
        }()
    }

原因是你在gofunc中使用了out迭代器变量,当gofunc要使用它时,循环就结束了。

这在 go/wiki/CommonMistakes 子主题 Using goroutines on loop iterator variables

下进行了描述

更多示例 - 阅读 this

更正后的循环应该如下所示,

    for _, out := range outs {
        wg.Add(1)

        go func(c <- chan int) {
            for result := range c {
                merge <- result
            }

            wg.Done()
        }(out)
    }

修复:

for _, out := range outs {
    wg.Add(1)

    out := out // <- add this

为什么?

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.