协程泄漏不是内存泄漏,而是goroutine卡住不退出导致其引用的大对象无法被GC回收;需先通过runtime.NumGoroutine()变化趋势(如请求后不回落、压测后不恢复)确认,再用/debug/pprof/goroutine?debug=2定位chan receive/select等阻塞堆栈。

协程泄漏不是内存泄漏,但会钉住内存
Go 里绝大多数“内存泄漏”现象,实际是 goroutine 卡住不退出,导致它栈上引用的 []byte、map、闭包捕获的结构体等对象无法被 GC 回收。你看到 heap_inuse 持续上涨,runtime.NumGoroutine() 同步爬升——这不是 GC 慢,是协程活埋了内存。
用 GoLand 快速确认是否真泄漏
别一上来就开 pprof heap,先看协程数变化趋势:
- 在 HTTP handler 入口、中间、返回前各加一行:
log.Printf("goroutines: %d", runtime.NumGoroutine()) - 单次请求后数字没回落(比如入口 85 → handler 内 92 → 返回后仍是 92),基本可断定泄漏已发生
- 压测结束等 30 秒再查,若比空闲态高 300+,且
/debug/pprof/goroutine?debug=2里大量出现chan receive或select状态,不用看 heap profile —— 就是协程泄漏
GoLand Profiler 一键抓阻塞协程堆栈
比手敲 curl 更快、更直观:
GoLand 2026.1.1 是 2026.1 发布后的首个维护修正版本,适合已经开始体验 2026.1 新功能并希望同步补丁的开发者。它更适合用于入门项目、现有项目迁移测试和 IDE 行为验证。
- 确保代码中已导入
_ "net/http/pprof",并在后台启动了http.ListenAndServe("localhost:6060", nil) - 点击菜单 Run → Start Profiling → Select Profiling Type → Goroutines → Start
- 等 10 秒后点 Stop,GoLand 自动渲染火焰图和 goroutine 列表
- 右键火焰图任意节点 → Filter by Stack Trace → 输入
chan receive或time.Sleep,立刻聚焦高发泄漏点
三类最常踩的坑及修复写法
定位到堆栈后,大概率落在以下三类调用链:
-
向已关闭 channel 发送数据:堆栈末尾含
runtime.chansend1,上层函数(如worker.go:45)启了 goroutine 却没做defer close(ch);panic 退出时 sender 忘关 channel,receiver 永久阻塞 -
context 未监听 Done:调用链夹着
http.Do、db.QueryRow或time.AfterFunc,但外围缺select { case ;闭包捕获的 ctx 可能已被 cancel,goroutine 还在死等 IO -
timer/ticker 未 Stop:堆栈含
timerproc或timeSleep,上层函数只defer ticker.Stop()—— panic 时不会执行;必须在select的退出分支里显式调用ticker.Stop()
真正难的不是发现,而是确认某个 goroutine 是否“本该退出却没退”。堆栈里出现 432000s(5 天)这种量级阻塞时间,基本就是它了;但要注意区分系统后台 goroutine(如 pprof、health check)的初始化抖动——盯住变化趋势,比绝对数值更重要。

















