goleak只用于测试而非线上,因其依赖runtime.Stack快照比对会阻塞调度器、拖慢程序且接口不稳定;最简启用方式是在TestMain中调用goleak.VerifyTestMain(m)并确保os.Exit(m.Run())在其后执行。

goleak 为什么只在测试中用,不能跑在线上
因为 goleak 本质是靠遍历运行时所有 goroutine 的栈信息做快照比对,会阻塞调度器、显著拖慢程序,且本身依赖 runtime.Stack 这类非稳定接口——线上环境禁用,Go 官方明确不保证其行为一致性。它只适合在单元测试结束前做一次轻量级断言。
如何在 test 文件里正确启用 goleak 检查
最简可行方式:在 TestMain 中包裹整个测试流程,并在退出前调用 goleak.VerifyNone。注意必须传入 t(*testing.T),否则 panic;且要忽略掉测试框架自身启动的 goroutine(比如 testify 或 gomock 相关)。
- 在
main_test.go中定义func TestMain(m *testing.M) - 调用
goleak.VerifyTestMain替代裸写VerifyNone,它会自动过滤已知测试辅助 goroutine - 确保
os.Exit(m.Run())在 Verify 之后执行,否则检查被跳过
func TestMain(m *testing.M) {
os.Exit(goleak.VerifyTestMain(m))
}常见 false positive 场景和绕过方法
goleak 默认把所有未结束的 goroutine 都当泄露,但很多是合法长期存活的(如日志 flusher、metrics collector、HTTP server)。直接忽略容易掩盖真问题,精准过滤更稳妥。
- 用
goleak.IgnoreTopFunction忽略已知安全的顶层函数,比如"net/http.(*Server).Serve"、"github.com/sirupsen/logrus.(*Entry).Writer" - 避免用模糊匹配(如
"log.*"),优先用完整函数签名,防止误杀 - 如果 goroutine 启动后立刻 sleep 等待信号,考虑加
time.Sleep(10 * time.Millisecond)让它稳定下来再拍快照
发现 leak 后怎么快速定位源头
goleak 报错只给 goroutine 栈,不指明是哪行代码启动的。关键看报错里最顶上的用户代码函数(不是 runtime 或 stdlib),再结合该函数中是否用了 go func() { ... }()、time.AfterFunc、未关闭的 context.WithCancel channel 等典型模式。
立即学习“go语言免费学习笔记(深入)”;
- 报错中若出现
goroutine 19 [chan receive],重点查有没有未消费的 channel 或忘了close() - 若栈里有
select { case 但 ctx 没被 cancel,说明 context 生命周期管理漏了 - 第三方库引发的 leak(如旧版
github.com/olivere/elastic)需升级或换库,别硬 ignore
真正难的不是检测,是区分「我写的 goroutine 忘了收尾」和「我依赖的库悄悄起了 goroutine 却没暴露控制接口」——后者往往得翻源码甚至打 patch。


















