Go语言用range遍历通道为何未接收全部值?代码问题解析
Go通道并发行为解析:为何90的平方未按预期输出?
以下代码是《The Go Programming Language》一书8.4节程序的修改版本:
package main import ( "fmt" ) func main() { naturals := make(chan int) squares := make(chan int) done := make(chan struct{}) // counter go func() { for x := 0; x <= 10; x++ { naturals <- x } done <- struct{}{} }() // squarer go func() { for x := range naturals { squares <- x * x } close(squares) }() // printer go func() { for x := range squares { fmt.Println(x) } }() <-done naturals <- 90 close(naturals) }
运行上述代码时,输出如下:
0
1
4
9
16
25
36
49
64
81
100
将代码最后几行修改为如下内容后:
naturals <- 90 <-done
输出变为:
8100
0
1
4
9
16
25
36
49
64
81
现象原因解析
要搞懂这个问题,必须抓住Go并发的几个核心规则:
- 无缓冲通道是同步的:发送方必须等接收方准备好才能完成发送,接收方也必须等发送方准备好才能完成接收。
- main goroutine退出即终止所有goroutine:只要main函数执行完,后台的goroutine不管有没有干完都会被强制停掉。
for range读取通道的逻辑:会一直从通道取数据,直到通道被关闭才会退出循环。
第一种情况(无8100输出)
代码最后执行顺序是:<-done → naturals <-90 → close(naturals)
<-done会阻塞main,直到counter goroutine把0-10全发送到naturals,然后向done发信号。此时counter已经退出。- main向
naturals发90:无缓冲通道要求squarer必须接收,squarer此时在range naturals循环里,会拿到90,算出8100并发送到squares。 - main关闭
naturals:squarer的range循环会因为通道关闭而退出,接着关闭squares。 - 关键问题:main在关闭
naturals后直接退出了!printer goroutine此时刚收到8100还没来得及打印,就被main退出带来的强制终止给打断了,所以8100没出现在输出里。
而0-10的平方能全部输出,是因为counter发送这些值时,squarer和printer是同步处理的:counter发一个数,squarer接收计算后发往squares,printer立刻接收打印,整个流程在counter发下一个数前就完成了,所以这些值都能在counter发done信号前打印完毕。
第二种情况(所有值都输出)
代码最后执行顺序是:naturals <-90 → <-done
- main先向
naturals发90:无缓冲通道下,squarer如果已经在等待接收,就会先拿到90,算出8100发往squares,printer同步接收并打印8100。 - main开始等待
<-done:这期间counter会持续发送0-10到naturals,squarer依次接收计算,printer同步打印这些平方值,直到counter发完所有数并向done发信号。 - main收到done信号后退出,此时所有该打印的值都已经被printer处理完毕,所以输出包含所有结果。
内容的提问来源于stack exchange,提问作者Navid
相关产品推荐
相关产品推荐

