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 1
但 Sending square 1
永远不会出现,我将不胜感激。另外,如果不打印Sending square 1
,output
通道是怎么关闭的。我只看到 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.
我创建了以下简单程序来测试使用通道的扇入扇出模式。它所做的是生成一些 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 1
但 Sending square 1
永远不会出现,我将不胜感激。另外,如果不打印Sending square 1
,output
通道是怎么关闭的。我只看到 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.