检测goroutine泄漏的核心思路是测试前后统计活跃数量并比对,结合runtime.NumGoroutine()、defer延迟检查、pprof分析及预防常见泄漏模式(如未关闭channel、未取消context等)来识别和修复。
检测 goroutine 泄漏的核心思路是:在测试前后统计活跃 goroutine 数量,若数量持续增长且无法回收,就可能存在泄漏。Golang 本身不提供自动泄漏报告,但可通过 runtime.NumGoroutine() + 测试断言 + 工具辅助来有效识别。
这是最轻量、最直接的检测方式,适合单元测试场景。
runtime.NumGoroutine() 记录初始值Close()、等待 WaitGroup 完成等)time.Sleep 短暂等待(如 10–50ms),让 runtime 有机会调度和回收避免重复写 sleep 和断言,可封装一个通用检测器:
func TestMyConcurrentFunc(t *testing.T) {
start := runtime.NumGoroutine()
defer func() {
// 给 runtime 留出回收时间
time.Sleep(10 * time.Millisecond)
if runtime.NumGoroutine() > start {
t.Errorf("goroutine leak: %d → %d", start, runtime.NumGoroutine())
}
}()
// 调用你的并发函数,例如:
MyConcurrentFunc()
}
注意:defer 中的 sleep 和检查必须放在测试逻辑之后,否则会提前执行。
进阶手段:pprof + net/http/pprof 实时观察
当手动计数难以定位源头时,启用 pprof 可查看当前所有 goroutine 的堆栈:
_ "net/http/pprof" 并启动 HTTP server(如 go http.ListenAndServe("localhost:6060", nil))http://localhost:6060/debug/pprof/goroutine?debug=2,获取完整 goroutine 列表及阻塞位置IO wait、chan receive、select 或长时间 sleep 的 goroutine —— 它们很可能卡在未关闭的 channel、无缓冲 channel 发送、或缺少 context 取消上多数 goroutine 泄漏源于“启动了却忘了收尾”。高频场景包括:
ctx.Done() 的 goroutine 不会退出。解法:始终在合适时机调用 cancel(),或用 defer cancel()
WaitGroup 显式等待,或通过 channel 收集完成信号
制:如 time.Tick 配合无限 for 循环。解法:改用 time.NewTicker + select { case
基本上就这些。不需要复杂工具也能发现大部分泄漏,关键是养成“启动必有回收”的编码习惯,并在测试中加入 goroutine 数量断言。