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 判断关闭,谁发送谁关闭。

相关推荐
Tomhex4 小时前
Golang内置函数总结
golang·go
XMYX-04 小时前
05 - Go 的循环与判断:语法、用法与最佳实践
开发语言·golang
被摘下的星星5 小时前
Go赋值操作的关键细节
开发语言·golang
喵了几个咪6 小时前
Go 语言 CMS 横评:风行 GoWind 对比传统 PHP/Java CMS 核心优势
java·golang·php
喵了几个咪6 小时前
Headless 架构优势:内容与展示解耦,一套 API 打通全端生态
vue.js·架构·golang·cms·react·taro·headless
Wenweno0o1 天前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
咬_咬1 天前
go语言学习(基本数据类型)
开发语言·学习·golang·数据类型
搜佛说1 天前
01-第1章-概述与快速开始
物联网·golang·开源·软件工程·边缘计算·嵌入式实时数据库
LlNingyu1 天前
什么是Go的接口(二)
golang