Go channel 深入解析
1. 为何不能只停留在语法层
只会写下面这种代码,其实不算真正理解 channel:
ch := make(chan int, 10) ch <- 1 v := <-ch _ = v真正的难点从来不是“怎么写”,而是“它在什么状态下会阻塞、什么时候会 panic、为什么 close 可以做广播、为什么有些 goroutine 会莫名其妙泄漏”。
Go 后端里,channel 一般出现在这几类 地方:
- 任务投递和 worker 协作。
- 请求超时与取消控制。
- 多 goroutine 之间的结果汇聚。
- 服务关闭时的广播通知。
- 有界并发控制。
这些场景背后,其实都不是“单纯传个值”那么简单,而是在依赖 channel 的同步语义和调度行为。
所以如果你只记住“channel 是管道”,其实是远远不够的。
你还得知道它什么时候像队列,什么时候像同步握手,什么时候像广播器,什么时候又会把 goroutine 卡死在原地…
2. 揭开channel的两面
如果只用一句话概括 channel,我会这么讲:
对外,channel 是带类型的通信管道;对内,它是锁 + 环形缓冲区 + 等待队列 + 唤醒逻辑。
这句话非常重要,因为它同时解释了两层东西。
第一层是语言语义:
你可以发送、接收、关闭、range、select,这些都是 Go 语言承诺给你的可用行为。
第二层是底层实现:
runtime 为了把这些语义落地,需要去维护:
- 一把锁,保证 channel 操作本身并发安全。
- 一个环形缓冲区,用来承接 buffered channel 的元素。
- 发送等待队列
sendq。 - 接收等待队列
recvq。 - 关闭标记和唤醒逻辑。
这也是为什么你表面上看到的是 ch <- x 和 <-ch,但实际发生的是一整套状态判断和调度行为。
较真的家伙,可以具体了解一下:后续还会在细讲,这张图可以先略微看下
3. 重点是 4 种状态
理解 channel,最先要记住的不是源码,而是状态。
我建议可以先把这 4 种状态背下来:
| 状态 | 发送 | 接收 | close |
|---|---|---|---|
nil channel | 永远阻塞 | 永远阻塞 | panic |
| 无缓冲 channel | 必须等接收方 ready | 必须等发送方 ready | 可以关闭 |
| 有缓冲 channel | buffer 未满可直接发送 | buffer 非空可直接接收 | 可以关闭,剩余数据仍可读 |
| 已关闭且已空 | panic | 立刻返回零值,ok=false | 重复 close panic |
这张表之所以重要,平时我们项目 遇到的,90%都源于此。
4. 四种状态,所衍生的四种行为
4.1 nil channel:
永远阻塞,却在 select 里很好用
未初始化 的 channel 零值就是 nil。
这种行为非常的 “绝”:
- 发送会永久阻塞。
- 接收会永久阻塞。
close(nil)<
