不会。go垃圾回收器仅回收堆上不可达对象,不处理阻塞的goroutine;后者仍持有栈空间和资源,持续存活导致协程泄露。

无缓冲 channel 阻塞后 goroutine 真的会被 GC 回收吗?
不会。Go 的垃圾回收器只管堆内存对象是否可达,不管 goroutine 是否卡住。ch 或 <code> 永久阻塞时,该 goroutine 仍持有栈、局部变量、channel 结构体引用,全部保持“活跃”状态,GC 完全无视。每个泄漏 goroutine 至少占 2KB 栈空间,高并发下几秒就压垮服务。
为什么 select + time.After 超时后还会 leak?
因为 time.After 只影响当前 goroutine 的控制流,它不取消已启动的子 goroutine,也不让 channel 发送“作废”。典型 leak 场景:
- 主 goroutine 启动
go func() { ch ,<code>ch是无缓冲的 -
select走了超时分支,主 goroutine 返回 - 子 goroutine 卡在
ch ,永远等不到接收者
修复不是加 default 或改 select 逻辑,而是让发送不依赖接收就绪——直接换带缓冲的 channel。
缓冲大小设为 1 就够用?还是得按并发数配?
缓冲大小取决于“最多可能有多少个值要发但没人收”。常见场景有明确上限:
- 单次异步操作(如
Read(url, timeout))→ 缓冲 1 足够,因为最多只发一次结果 - 批量广播错误收集(如
broadcastMsg(...))→ 缓冲大小应 ≥len(addrs),否则第n+1个 goroutine 仍会卡在errc - 若不确定上限或怕误估,优先用
context配合带缓冲 channel,而非盲目放大缓冲区
注意:缓冲太大可能掩盖设计问题,比如本该用 for range ch + close(ch) 的同步流程,硬撑成大缓冲异步队列,反而更难维护。
关闭 channel 能解决所有 leak 吗?
不能。关闭只对接收方有效(for range ch 自动退出,v, ok := 中 <code>ok 为 false),但对发送方是 panic 触发器:send on closed channel。所以必须遵守单一发送者原则:
- 谁创建、谁发送、谁关 —— 接收方绝不调用
close(ch) - 多个 goroutine 往同一 channel 发?别关它,改用信号 channel(如
stopCh)通知各发送者自行退出 - HTTP 客户端、数据库连接池等第三方库内部也用 channel,它们的 leak 往往藏在
Close()忘调,而不是你写的 channel 逻辑里
最易被忽略的一点:泄漏往往不出现在你盯着写的那几行 channel 操作里,而是在你认为“反正很快就会有人读”的那个无缓冲 channel 上——它只要漏掉一次接收,整个 goroutine 就钉死在那里,再也不会动。











