协程泄漏必然发生,关键在于暴露时机;需通过 runtime.numgoroutine() 基线对比、goleak 自动检测、pprof 定位卡点,并确保每个 goroutine 有明确退出路径。

协程泄漏不是“会不会发生”的问题,而是“什么时候暴露”的问题——只要没显式退出路径,它就在那里。
用 runtime.NumGoroutine() 快速抓出明显泄漏
这是最轻量、最直接的“第一道筛子”,适合本地跑单元测试或 CI 阶段快速拦截。它不告诉你哪一行漏了,但能立刻告诉你:“你写的这段逻辑,启动的 goroutine 没收拾干净。”
- 关键不是看绝对值,而是看「操作前后是否回归基线」:调用前记一次
runtime.NumGoroutine(),等足够久(比如time.Sleep(100 * time.Millisecond)),再记一次;差值超过 2~3 就要警惕 - 别只 sleep 一次就下结论——有些 goroutine 靠超时退出,有些等外部信号,建议配合
done chan struct{}或time.AfterFunc显式确认退出时机 - 系统 goroutine 有波动(GC、timer 等),采样 3 次取最小值作 baseline 更稳;或者直接用
goleak,它默认过滤掉已知安全的 stdlib 协程 - 别在
init()或包变量初始化里拉起 goroutine——goleak.VerifyNone(t)会把它当泄漏报出来
用 goleak.VerifyNone(t) 在测试中自动揪出残留协程
它不是“导入就生效”的魔法库,而是需要你显式调用的守门人。适合集成进 Test 函数末尾,作为自动化防线。
- 必须写成
defer goleak.VerifyNone(t),否则 test panic 时不会执行,等于白加 - 如果你的测试里合法启了后台 HTTP server,得加
goleak.IgnoreCurrent(),否则它会把 server 的监听协程当成泄漏 - 它默认只检查测试执行期间新增的协程,不扫描整个进程生命周期;若需更广覆盖,得配合
goleak.Cleanup()和自定义goleak.Option - 常见误配:忽略函数名写错,比如写成
IgnoreTopFunction("http.(*Server).Serve")却漏了包路径,实际应为IgnoreTopFunction("net/http.(*Server).Serve")
用 net/http/pprof 查线上服务里卡死的协程在哪
当服务跑着跑着协程数从 50 涨到 800,NumGoroutine() 只会说“有事”,而 pprof 告诉你“什么事、在哪行、为什么卡”。它是生产环境定位泄漏的标配手段。
- 启动时注册:
import _ "net/http/pprof",再起一个 goroutine 跑http.ListenAndServe("localhost:6060", nil) - 访问
http://localhost:6060/debug/pprof/goroutine?debug=2,重点关注状态为IO wait、chan receive、chan send的堆栈 - 对比两次 dump:一次是刚启动,一次是协程数明显上涨后,用 diff 工具看新增堆栈,直指问题函数
- 注意:不要长期开着
/debug/pprof路由对外暴露,尤其在生产环境;可通过反向代理加鉴权或临时开启
预防泄漏的核心动作:每个 goroutine 都要有明确退出路径
90% 的泄漏本质是“没有退出机制”。写 for {} 或 for range ch 却不检查 ctx.Done(),等于埋雷。
- 永远用
context.WithCancel或context.WithTimeout创建子 context,并确保在合适时机调用cancel()——忘记调用,等于没加 - 对 channel 操作,优先用
select { case ,而不是裸写 <code> - 第三方库启动的 goroutine(如 pgx、sarama、opentelemetry-go)常在初始化时自动启后台 goroutine;务必查阅文档,确认是否有
Close()、Stop()或Shutdown()方法,并在服务退出前统一调用 - HTTP 客户端必须设置超时:用
http.NewRequestWithContext(ctx, ...),禁用全局无 timeout 的 client;自定义http.Server须配置ReadTimeout、WriteTimeout、IdleTimeout
真正难的不是发现泄漏,而是判断“这个协程到底该不该退出”——比如一个长连接心跳 goroutine,在连接断开时退出是对的;但在重连过程中反复启停,又可能造成新的泄漏。这类边界逻辑,光靠工具扫不出来,得靠设计时就画清生命周期图。











