最直接信号是测试后进程不退出或go test -race报“found goroutine”;推荐用defer goleak.verifynone(t)检测,它比对测试前后goroutine快照并报告未退出协程及其调用栈。

单元测试里发现 goroutine 泄露,最直接的信号是测试跑完进程不退出、go test -race 报 “found goroutine”,或者 runtime.NumGoroutine() 在测试前后差值不为零——这说明有 goroutine 没被回收,还在等 channel、timer 或锁。
用 goleak.VerifyNone(t) 拦截新增 goroutine
这是目前最轻量也最可靠的测试期检测手段。它不依赖猜测,而是比对测试开始前和结束后的 goroutine 快照,报告“新增但未退出”的协程及其初始调用栈。
-
goleak.VerifyNone(t)必须放在defer里,否则测试 panic 时不会执行,漏检 - 安装:
go get -u github.com/uber-go/goleak - 典型写法:
defer goleak.VerifyNone(t)放在测试函数开头即可 - 它默认忽略标准库启动的 goroutine(如
net/http监听协程),但如果你手动启了http.Server或用了time.AfterFunc,得显式忽略:goleak.IgnoreCurrent()或用goleak.Option过滤掉已知安全堆栈 - 别 ignore
testing.(*T).Run—— 这是框架自身,不是你的代码
手动比对 runtime.NumGoroutine() 差值
适用于不想引入第三方、或需要更细粒度控制的场景。它不依赖快照对比逻辑,而是靠两次计数断言,简单粗暴。
- 在测试开头记下当前数量:
before := runtime.NumGoroutine() - 测试逻辑执行完后加断言:
if runtime.NumGoroutine() > before { t.Fatalf("goroutine leak: %d → %d", before, runtime.NumGoroutine()) } - 允许 ±1~2 的浮动(比如 pprof 启动、日志采集等短暂 goroutine)
- 注意:这个方法只对“测试结束时仍存活”的 goroutine 有效;如果 goroutine 短暂启动又退出,但频率高,得靠
pprof或go tool trace - 避免用
time.Sleep等待 goroutine 退出——时间短了没跑完,时间长了拖慢测试
避开 time.After / time.Tick 这类静默泄露源
测试中 time.After 和 time.Tick 是高频泄露点,因为它们背后藏着永不退出的 goroutine,且没有 Stop 方法。
- 测试中一律改用
time.NewTimer+Stop():t := time.NewTimer(10 * time.Millisecond); defer t.Stop() -
time.Tick绝对禁止在测试中使用——它底层 timer goroutine 永不退出 - 如果必须用定时逻辑,把 timer 封装进结构体,实现
Close()方法,并在defer中调用 - 所有在
SetupTest或TestXxx中启动的 goroutine,必须绑定到该测试生命周期——推荐用context.WithCancel或显式done chan struct{}
为什么 pprof 在测试阶段帮不上忙
/debug/pprof/goroutine?debug=2 是运行时诊断工具,它需要服务持续运行并暴露 HTTP 端口。单元测试生命周期太短,端口来不及监听、快照来不及抓取,基本无效。
- 测试阶段无法起
http.ListenAndServe,除非你用httptest.NewUnstartedServer并手动管理生命周期 - 即使强行起服务,测试结束时 goroutine 可能还没来得及调度到阻塞点,快照看到的是“正常”状态
-
goleak和手动计数才是测试阶段唯一靠谱的守门员;pprof是上线后或集成测试中才真正发力的定位工具 - 别指望
go test -v日志里看到 goroutine 堆栈——它不输出这些信息
真正难缠的不是那些一眼能认出的泄漏,而是那些“看起来会自己结束”的 goroutine:比如用 select 等 ctx.Done(),但忘了在循环里反复检查;或者用 for range ch,却没确保发送方一定会 close(ch)。这些细节在测试里最容易被忽略,也最难靠工具自动捕获。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











