Go语言中select读取已关闭通道为何不阻塞且出现死循环?
Go中Select遇到已关闭通道的死循环问题解析
嘿,我来帮你捋清楚这个问题——你遇到的死循环其实是Go语言里已关闭通道的特性在搞鬼!
先拆解下你的代码流程:你创建了一个带缓冲的通道,塞了4个整数后把通道关闭了。一开始select正常取出1、2、3、4,但通道关闭后,怪事就来了:case c := <-ch这个分支会永远处于就绪状态,每次都能立刻拿到int类型的零值0,完全不会阻塞,所以select根本不会走到default分支去sleep,直接进入下一轮循环,无限输出0。
这是因为Go的规则是:当通道被关闭后,再从通道接收数据,会立刻返回对应类型的零值,而且这个操作永远不会阻塞。你的select里这个case一直就绪,自然就把default分支彻底跳过了,形成死循环。
解决方法有两种,看你需求选:
方法一:检查通道接收的ok状态
修改select里的case,利用通道接收的第二个返回值判断通道是否关闭:
func TestSelect(t *testing.T) { ch := make(chan int, 10) ch <- 1 ch <- 2 ch <- 3 ch <- 4 close(ch) for { select { case c, ok := <-ch: if !ok { fmt.Println("通道已关闭,退出循环") return // 或者break跳出循环 } fmt.Println("out... ", c) default: fmt.Println("sleep... ") time.Sleep(1 * time.Second) } } }
当通道关闭后,ok会变成false,这时我们就可以退出循环,避免死循环。
方法二:用range遍历通道(更简洁)
如果你只是想读取通道里所有数据,通道关闭后自动结束,用range遍历是更优雅的写法,它会在通道关闭后自动退出循环:
func TestSelect(t *testing.T) { ch := make(chan int, 10) ch <- 1 ch <- 2 ch <- 3 ch <- 4 close(ch) // range会自动处理通道关闭,遍历完所有数据后退出 for c := range ch { fmt.Println("out... ", c) } fmt.Println("通道已关闭,循环结束") }
总结一下:你之前的认知没错——正常的未关闭通道在无数据时,select的接收分支会阻塞,但通道关闭后就完全不一样了,接收操作会一直返回零值,这才是导致死循环的核心原因。
内容的提问来源于stack exchange,提问作者Mr IF
相关产品推荐
相关产品推荐

