Go 面试:channel 关闭后为什么还能读到值?worker 怎么退出
我见过一个 worker 停不下来的小事故。上游已经执行了 close(jobs),日志却还在不断打印 0。排查到最后不是 goroutine 没收到关闭信号,而是接收端把“读到零值”当成了一份真任务。
close 不会把缓冲区里的数据清掉。它只是宣布以后不能再发送。已经排队的数据照样会被接收端取走,直到队列真的空了。
拿一个容量为 2 的 channel 跑一下就很直观:
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 7
ch <- 9
close(ch)
for {
v, ok := <-ch
fmt.Printf("v=%d, ok=%v\n", v, ok)
if !ok {
return
}
fmt.Println("handle:", v)
}
}输出会是:
v=7, ok=true
handle: 7
v=9, ok=true
handle: 9
v=0, ok=false前两次是缓冲区里原本就有的 7 和 9。第三次才是关闭后的接收结果:int 的零值 0,配合 ok=false。
很多人只写成这样:
for {
job := <-jobs
handle(job)
}它不会因为 channel 关闭而退出。关闭后的接收立即返回零值,于是这个循环会拿着 0 一直跑。要是 job 是指针,得到的是 nil;要是是结构体,拿到的则是一份零值结构体。问题通常不会立刻 panic,反而更难发现。
worker 只是消费一批任务时,我更常用 range:
for job := range jobs {
handle(job)
}它会把缓冲区里剩下的任务消费完,然后自然结束。只有当代码确实要区分“收到的值”和“channel 已关闭”时,才展开写 v, ok := <-ch。
面试里再追问一句“谁来 close”,别答谁方便谁关。通常由发送方、或者明确拥有发送权的一侧关闭。接收方不知道后面还有没有生产者;随手关掉,很容易让仍在发送的 goroutine 直接 panic。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。