当 Go 的 channel 被 close 后读写操作会怎么样?

当 Go 的 channel 被 close 后,对它的读操作行为有以下明确规则:
重点行为总结
| 操作 | 行为 |
|---|---|
| 从已关闭且仍有缓存数据的 channel 读 | 会正常返回该数据 |
| 从已关闭且缓冲区数据已读完的 channel 读 | 返回 零值(基于类型)和 ok=false |
| 向已关闭的 channel 写数据 | ❌ 直接 panic |
如果 channel 没有缓冲 和有缓冲的读完数据后的操作是一样的结果
示例代码
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)v, ok := <-ch // v=1, ok=true
v, ok = <-ch // v=2, ok=true
v, ok = <-ch // v=0, ok=false
range 遍历已关闭 channel
当你使用 range 遍历 channel 时,它会一直读到 channel 关闭且数据读完后才退出循环:
for v := range ch {fmt.Println(v)
}
fmt.Println("done")
零值返回示例
如果 channel 是 chan string,零值读出来就是 ""
如果是 chan *User,零值就是 nil
为什么要设计成这样?
- 避免读端阻塞
- 让接收端可以用
ok判断数据是否结束 - 使 channel 更像是一个有限流(stream)
典型使用场景
生产者关闭 channel 通知消费者不再有新数据
func producer(out chan<- int) {defer close(out)for i := 0; i < 5; i++ {out <- i}
}func consumer(in <-chan int) {for v := range in {fmt.Println(v)}
}
常见误区
| 误区 | 实际情况 |
|---|---|
| close 后读会 panic | ❌ 永远不会 panic |
| 必须由接收方 close | ❌ 通常是发送方 close |
| close 可重复调用 | ❌ 重复 close 会 panic |
完整示例代码
package mainimport ("fmt"
)func main() {ch := make(chan int, 3)// Producergo func() {for i := 0; i < 5; i++ {ch <- i}close(ch) // 通知消费者:不再有新数据}()// Consumerfor v := range ch { // 会一直读到 ch 被关闭 & 数据读完fmt.Println("received:", v)}fmt.Println("done") // 会执行,因为 range 自动退出
}
输出示例(顺序可能不同)
received: 0
received: 1
received: 2
received: 3
received: 4
done
