goroutine 泄露可通过进程不退出、-race 警告、numgoroutine 差值非零发现;需在测试前后比对 goroutine 数并断言,setuptest 中异步操作易累积泄露,time.after 等易静默泄露。

怎么发现测试里有 Goroutine 泄露
Go 测试运行完,进程没立刻退出,或者 go test -race 报出“found goroutine”警告,基本就是泄露了。更隐蔽的是测试通过但内存缓慢增长、runtime.NumGoroutine() 在测试前后差值不为零——这说明 goroutine 没被回收,还在等 channel、timer 或锁。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在测试函数开头记下当前 goroutine 数:
before := runtime.NumGoroutine() - 测试逻辑执行完后加断言:
if runtime.NumGoroutine() > before { t.Fatalf("goroutine leak: %d → %d", before, runtime.NumGoroutine()) } - 注意:这个方法只对“测试结束时仍存活”的 goroutine 有效;如果 goroutine 短暂启动又退出,但频率高,得靠 pprof 或
go tool trace
为什么 testify/suite 和 SetupTest 容易埋雷
用 testify/suite 时,SetupTest 里启的 goroutine 如果没配超时或没关 channel,会在每个子测试间累积。比如在 SetupTest 启一个监听 time.After 的 goroutine,但没用 select + done 通道控制生命周期,它就一直挂着。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有在
SetupTest或TestXxx中启动的 goroutine,必须绑定到该测试的生命周期——推荐用context.WithCancel或显式done chan struct{} - 避免在
SetupTest里做异步初始化(如启动 HTTP server、监听端口),改用 lazy init +sync.Once或挪到TestMain统一管理 - 如果用了
httptest.NewUnstartedServer,记得调s.Start()和s.Close(),否则底层 listener goroutine 不会退出
time.After 和 time.Tick 是静默泄露大户
time.After 返回的 channel 背后有个永不退出的 goroutine,只要 channel 没被接收完,它就一直占着。在测试里写 select { case 看似无害,但如果测试提前 return 或 panic,这个 timer 就泄漏了。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 测试中一律用
time.NewTimer+Stop(),不用time.After;例如:t := time.NewTimer(10 * time.Millisecond); defer t.Stop() -
time.Tick绝对禁止在测试中使用——它没有 Stop 方法,且底层 timer goroutine 永不退出 - 如果必须用定时逻辑,把 timer 封装进结构体,实现
Close()方法,并在defer中调用
pprof 和 runtime.Stack 怎么快速定位泄露点
光靠数字差值只能知道“有泄露”,不知道“在哪”。这时候要抓 goroutine stack。测试跑完不退出时,访问 /debug/pprof/goroutine?debug=2(需注册 net/http/pprof)能看到所有 goroutine 当前调用栈;或者直接在测试末尾调 runtime.Stack 打印。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在怀疑泄露的测试末尾加:
buf := make([]byte, 2<<20) n := runtime.Stack(buf, true) t.Log("active goroutines:\n" + string(buf[:n])) - 重点关注状态为
chan receive、select、semacquire的 goroutine,它们大概率卡在 channel、mutex 或 condvar 上 - 如果看到大量
time.sleep或runtime.gopark,检查是否漏了Stop()或close()
真正难的不是检测,是判断哪个 goroutine “本该结束却没结束”——得结合业务逻辑看 channel 是否被 close、context 是否被 cancel、锁是否被释放。这些地方一旦漏掉,泄露就藏得特别深。










