goroutine泄漏是go服务内存持续上涨的主因,表现为协程卡在chan receive、select等阻塞点不退出,需监听ctx.done()、正确关闭channel、配对stop timer/ticker,并通过goleak或numgoroutine测试左移拦截。

goroutine 本身没有“手动回收”机制,退出后由 runtime 隐式清理;但若它卡在阻塞状态(如 chan receive、select、time.Sleep 或未响应 ctx.Done()),就不会退出,也就不会释放其栈和持有的对象——这才是内存缓慢上涨的真正源头。
runtime.NumGoroutine() 怎么看才不误判
刚启动时 runtime.NumGoroutine() 跳到 100+ 很正常,那是 pprof、health check、log 等后台 goroutine 在初始化。真要盯的是这三种情形:
- 单次 HTTP 请求处理完,goroutine 数没回落(比如入口是 85,
w.WriteHeader后还是 85+) - 压测结束 30 秒后,总数仍比空闲态高 30% 以上
- 服务稳定运行几小时,数字从 60 → 240 → 960 这样单调上升
建议在 handler 入口、出口、以及空闲时各打一行日志:log.Printf("goroutines@start: %d", runtime.NumGoroutine()),只采一次值毫无意义。
/debug/pprof/goroutine?debug=2 为什么必须加 ?debug=2
?debug=1 只返回统计摘要(比如 “234 goroutines in chan receive”),看不出谁卡在哪、卡了多久;?debug=2 才输出完整调用栈和阻塞时长,这是定位泄漏的核心依据。
重点关注这些状态:
-
chan receive (nil chan):监听了未初始化或已关闭的 channel -
select卡在某个分支且无default或未响应ctx.Done() -
semacquire:锁没释放、sync.WaitGroup忘记Done() -
IO wait:比如io.ReadFull卡住、http.Response.Body没Close()导致底层连接池无法复用
如果看到几百个 goroutine 全卡在 golang.org/x/crypto/ssh 或 net/http.readLoop,阻塞时间显示 “432000s”(5 天),基本不用再查,就是它。
goleak.VerifyNone(t) 在测试里为什么常失效
它靠比对测试前后 goroutine 快照来发现“新增但未退出”的协程,但有三个典型坑:
- 用了
t.Parallel():goroutine 生命周期可能跨测试边界,goleak会漏检 - 第三方库内部启了 goroutine(如
http.Server、time.AfterFunc),没 mock 就直接误报——得用goleak.IgnoreTopFunction("net/http.(*Server).Serve")显式过滤 - 测试里启了
time.Ticker但没Stop(),或者go func() { for range ch { ... } }()却没人关ch,这类问题它能当场揪出,但前提是测试逻辑本身不干扰快照采集
更稳妥的做法是:在 TestMain 里用 goleak.VerifyTestMain(m),并确保所有 goroutine 在测试结束前显式退出(比如 cancel()、close(ch))。
goroutine 泄漏和内存泄漏怎么区分
它们经常被混为一谈,但根源完全不同:
- goroutine 泄漏:表现为
/debug/pprof/goroutine?debug=2中大量状态为chan receive或select的 goroutine,数量随请求线性增长;它不直接占堆,但会钉住闭包里的大对象(比如含[]byte的结构体),导致 heap_inuse 慢慢涨 - 内存泄漏:表现为
runtime.MemStats.HeapInuse持续单向增长,且go tool pprof -inuse_space显示某类对象(如*http.Request)的驻留内存线性上升;此时要重点查 map/sync.Map 是否无限写入、HTTP handler 是否闭包捕获了大变量、或json.Unmarshal后存进全局缓存却没清理
最常被忽略的一点:一个 goroutine 卡住,可能让整个连接池、缓冲区、甚至 TLS session 都无法释放——它不是“多占了几 KB 栈”,而是拖垮了整条资源链。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











