最直接方法是用 pprof 查 goroutine 阻塞点;若程序运行中内存上涨、响应变慢且 runtime.NumGoroutine() 持续上升,大概率是通道未关闭导致 goroutine 卡住。

用 pprof 查 goroutine 阻塞点最直接
如果程序跑着跑着内存涨、响应变慢,runtime.NumGoroutine() 持续上升,大概率是通道没关导致 goroutine 卡在 上。这时候不用翻代码,直接用 <code>pprof 抓堆栈:
- 启动时加
http.ListenAndServe("localhost:6060", nil)并导入net/http/pprof - 运行后访问
http://localhost:6060/debug/pprof/goroutine?debug=2 - 重点看堆栈里反复出现的
chan receive—— 它不是“正在接收”,而是“永远等不到下一条”
这种卡住的 goroutine 通常对应一个未关闭的通道,且没有其他协程往里写、也没人主动退出。
goleak 在测试中自动揪出泄漏 goroutine
单元测试里最容易漏掉通道关闭逻辑,goleak 是专治这个的。它不依赖你手动检查,而是测完自动报残留 goroutine:
- 测试函数开头加
defer goleak.VerifyNone(t)(必须 defer,否则 panic 时不执行) - 若测试失败并提示类似
found unexpected goroutines: [chan receive],说明某个通道没关干净 - 注意:
goleak.IgnoreCurrent()可过滤掉已知安全的协程(比如你手动启的http.Server),但别滥用,掩盖真问题
它不会告诉你哪个通道没关,但能确认“这里一定有未关闭通道引发的泄漏”,缩小排查范围很有效。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
静态扫描:grep + 看 close() 调用是否唯一可信
运行时手段只能发现问题,真正修复得回代码里定位。关键不是找“有没有 close(ch)”,而是确认“谁调、何时调、能不能保证只调一次”:
- 搜
close(,检查每个调用是否都在发送逻辑末尾,且前面没有重复 close 或 panic 跳过 - 多个 goroutine 往同一
ch写?那它们中间必须有协调者(比如sync.WaitGroup+defer close(ch)),不能各自判断“我发完了就关” - 函数参数是
ch chan?说明它是只写通道,调用方大概率不该关;若是 <code>ch chan int,则需确认该函数确实是唯一发送者
很多“已关但还泄漏”的 case,其实是 close 太早——比如无缓冲通道上,close(ch) 紧跟在 go func() { ch 后面,发送 goroutine 还没执行到 <code> 就 panic 了,主 goroutine 却以为关成功了。
for range 接收端无法主动感知通道状态
别写这种代码:if len(ch) == 0 && !isClosed(ch) { ... } —— Go 没有 isClosed(),len(ch) 只反映缓冲区长度,和关闭无关。接收端唯一可靠信号是 ok 值:
v, ok := 中 <code>ok == false才表示通道已关且缓冲为空-
for range ch本质就是持续做直到 <code>ok == false,它不提供“中途检查是否已关”的能力 - 真需要边收边判断,得用
select+donechannel 控制生命周期,而不是依赖通道自身关闭状态
通道关闭不是“资源释放命令”,而是“数据流结束信号”。把关闭当成清理动作来用,八成会出竞态;把它当成业务语义的一部分(比如“所有任务已下发完毕”),才不容易踩坑。

















