你会彻底搞懂:
- 什么时候能关?什么时候不能关?
- 关闭后通道会发生什么?
- 关闭后发送/接收/
range/select行为 - 如何安全关闭(避免 panic)
- 关闭的底层原理
- 90% 人都会踩的关闭陷阱
一、close() 到底是什么?
close(ch) 是 Go 内置函数,作用:
标记通道不再接受新数据,仅允许读取剩余数据。
它不是"销毁通道",通道是引用类型,关闭后依然存在,只是状态变为 closed。
函数签名:
go
func close(c chan<- T)
二、关闭通道的核心规则(死记)
- 只能关闭双向通道 或 只写通道
- 关闭后不能再发送数据(发送直接 panic)
- 关闭后可以继续接收数据(读完返回零值)
- 不能关闭只读通道(编译错误)
- 不能重复关闭(重复 close 直接 panic)
- 不能关闭 nil 通道(panic)
- 谁发送,谁关闭(接收方永远不要关闭通道)
三、关闭后各种操作的行为(最重要)
1. 关闭后 发送数据
go
close(ch)
ch <- 123 // ❌ panic: send on closed channel
2. 关闭后 接收数据
分两种语法:
语法1:v := <-ch
- 有数据 → 正常读
- 无数据 → 返回类型零值
go
close(ch)
v := <-ch // 0 (int零值), false(布尔零值), ""(字符串零值)
语法2:v, ok := <-ch
- 有数据 →
v=数据, ok=true - 无数据 →
v=零值, ok=false
ok=false 是判断通道关闭且读完的唯一标准
3. 关闭后 for range 遍历
go
for v := range ch {
}
行为:
- 读完通道所有数据
- 通道关闭 → 自动退出循环
- 通道未关闭 → 永久阻塞(goroutine 泄漏)
4. 关闭后 select
- 关闭且空的通道:case 永远就绪,返回零值
- nil 通道:case 永远阻塞
四、关闭 nil 通道会怎样?
go
var ch chan int // nil
close(ch) // ❌ panic: close of nil channel
五、重复关闭会怎样?
go
ch := make(chan int)
close(ch)
close(ch) // ❌ panic: close of closed channel
六、关闭通道的底层原理(简单易懂版)
通道底层结构体 hchan 有一个字段:
go
closed uint32 // 0=开启 1=关闭
执行 close(ch) 时:
- 加锁
- 设置
closed=1 - 唤醒所有阻塞在接收的 goroutine(返回零值)
- 唤醒所有阻塞在发送的 goroutine(让它们 panic)
- 解锁
七、安全关闭通道的 4 种标准方案(生产可用)
方案1:单发送者 → 最简单
谁发数据,谁最后关闭。
go
func producer(ch chan<- int) {
defer close(ch) // 发送完自动关闭
for i := 0; i < 3; i++ {
ch <- i
}
}
方案2:多发送者 → sync.Once(最常用)
确保只关闭一次
go
var once sync.Once
func safeClose(ch chan int) {
once.Do(func() { close(ch) })
}
方案3:多发送者 + 专门关闭 goroutine
用 sync.WaitGroup 等所有发送者完成,再关闭。
方案4:使用 context 取消(大型服务推荐)
不主动关闭,而是用 ctx.Done() 通知退出。
八、90% 的人踩过的关闭陷阱
陷阱1:接收方关闭通道(致命)
go
// 接收方关闭 → 发送方会panic
ch := make(chan int)
go func() {
<-ch
close(ch) // ❌ 接收方关闭!
}()
ch <- 1 // panic
陷阱2:关闭后继续发送
go
close(ch)
ch <- 1 // panic
陷阱3:不知道通道已关闭,一直发送
陷阱4:for range 不关闭通道 → goroutine 泄漏
go
for v := range ch { // 永远阻塞
}
陷阱5:用零值判断通道关闭(错误)
go
v := <-ch
if v == 0 { // ❌ 错误!数据本身可能就是0
// 通道关闭?
}
正确判断:
go
v, ok := <-ch
if !ok {
// 通道已关闭
}
九、面试高频题(你一定能答对)
Q1:关闭一个通道会发生什么?
- 不能再发送
- 可以接收
- 接收完返回零值
- for range 自动退出
- 重复关闭 panic
Q2:关闭 nil 通道?
panic
Q3:如何判断通道关闭?
v, ok := <-ch,ok=false 表示关闭且读完。
Q4:谁负责关闭通道?
发送方。
Q5:关闭通道会释放内存吗?
不会,只是改变状态。只有没有任何 goroutine 引用时才会被 GC。
Q6:向关闭通道发送会怎样?
panic
十、终极总结(一句话记住)
close(ch) 只做一件事:标记通道不再写入。
关闭后可读不可写,接收用 ok 判断关闭,谁发送谁关闭。