Go Channel `close()` 深入全面讲解

你会彻底搞懂:

  • 什么时候能关?什么时候不能关?
  • 关闭后通道会发生什么?
  • 关闭后发送/接收/range/select 行为
  • 如何安全关闭(避免 panic)
  • 关闭的底层原理
  • 90% 人都会踩的关闭陷阱

一、close() 到底是什么?

close(ch) 是 Go 内置函数,作用:
标记通道不再接受新数据,仅允许读取剩余数据。

它不是"销毁通道",通道是引用类型,关闭后依然存在,只是状态变为 closed

函数签名:

go 复制代码
func close(c chan<- T)

二、关闭通道的核心规则(死记)

  1. 只能关闭双向通道 或 只写通道
  2. 关闭后不能再发送数据(发送直接 panic)
  3. 关闭后可以继续接收数据(读完返回零值)
  4. 不能关闭只读通道(编译错误)
  5. 不能重复关闭(重复 close 直接 panic)
  6. 不能关闭 nil 通道(panic)
  7. 谁发送,谁关闭(接收方永远不要关闭通道)

三、关闭后各种操作的行为(最重要)

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) 时:

  1. 加锁
  2. 设置 closed=1
  3. 唤醒所有阻塞在接收的 goroutine(返回零值)
  4. 唤醒所有阻塞在发送的 goroutine(让它们 panic)
  5. 解锁

七、安全关闭通道的 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 := <-chok=false 表示关闭且读完。

Q4:谁负责关闭通道?

发送方

Q5:关闭通道会释放内存吗?

不会,只是改变状态。只有没有任何 goroutine 引用时才会被 GC。

Q6:向关闭通道发送会怎样?

panic


十、终极总结(一句话记住)

close(ch) 只做一件事:标记通道不再写入。
关闭后可读不可写,接收用 ok 判断关闭,谁发送谁关闭。

相关推荐
William.csj4 小时前
BaiduPCS-Go——下载百度网盘文件的工具
golang·baidupc
kebeiovo7 小时前
游戏服务端开发:Actor模型详解(Go语言)
开发语言·后端·golang
Wang's Blog10 小时前
Go-Zero项目开发9: 微服务治理之服务注册中心
微服务·golang
布朗克16811 小时前
Go 入门到精通-27-泛型编程
开发语言·golang·xcode·泛型编程
名字还没想好☜11 小时前
Go 用 errgroup 管理并发子任务:错误收敛、取消传播与限流
开发语言·后端·golang·go·并发
web守墓人15 小时前
【go语言】gotar:使用go语言复刻tar命令,并加入7z支持,可独立运行在windows、linux、macos上
linux·macos·golang
看-是灰机15 小时前
使用go语言实现对接
linux·开发语言·后端·docker·语言模型·golang·飞书
Wang's Blog17 小时前
Go-Zero项目开发8: 构建社交API服务与微服务解耦实践
开发语言·微服务·golang
怒放de生命20101 天前
【web3基础】Go-Zero+mysql进行crud操作(三)
mysql·golang·web3·go-zero
名字还没想好☜1 天前
Go 的 time.Ticker 陷阱:定时任务里被忽略的内存泄漏与正确关闭
java·数据库·golang·go·定时器