goroutine泄漏最直接信号是runtime.numgoroutine()持续单向增长且不回落,需结合/debug/pprof/goroutine?debug=2查看大量阻塞在chan receive、netpoll或semacquire的堆栈,卡住超10秒基本可判定泄漏。

如何用 pprof 实时观测 goroutine 泄漏
goroutine 数量持续上涨是高并发服务最危险的信号,不是“慢”,而是“正在失控”。别等报警才查,启动后立刻记下 runtime.NumGoroutine() 基线值(通常 3–6),压测 2 分钟后再查——若涨到几百上千且不回落,基本可判定泄漏。
关键检查点:
- 访问
/debug/pprof/goroutine?debug=2,重点扫三类堆栈:chan receive、netpoll、semacquire。卡住超 10 秒的,99% 是没监听ctx.Done()或 channel 没关 - 用
go tool pprof http://localhost:6060/debug/pprof/block看阻塞热点;若chan send占比 >40%,说明任务生产者远快于消费者,channel 缓冲区太小或 worker 不够 - 别只看数量,要看堆栈是否重复出现同一模式(比如全卡在
http.Get后没调resp.Body.Close())
为什么 expvar 统计并发任务数容易失真
用 expvar.NewInt("active_requests") 这类手动计数看似直观,但实际极易出错:goroutine panic、提前 return、context cancel 都可能让 Inc() 和 Dec() 不成对,导致统计值漂移甚至负数。
更稳的做法是用 sync/atomic + 固定生命周期标记:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 在 handler 入口用
atomic.AddInt64(&activeCount, 1) - 必须在 defer 里配对
atomic.AddInt64(&activeCount, -1),且不能依赖闭包变量(避免被优化掉) - 配合
pprof的 goroutine profile 交叉验证,而非单独信任 expvar 数值
HTTP 中间件里埋点监控并发瓶颈的实操陷阱
很多框架中间件喜欢在 next(c) 前后打时间戳算耗时,但在高并发下这会掩盖真实瓶颈——因为耗时长未必是业务慢,可能是 goroutine 在排队等 worker、channel 阻塞、或锁竞争。
真正有用的中间件监控应包含:
- 记录每个请求进入时的
runtime.NumGoroutine()快照,对比峰值变化 - 用
time.Now().Sub(start)只测 handler 执行时间,**不包括**等待协程池分配的时间 - 对关键 channel(如任务队列)暴露
len(ch)和cap(ch),实时看积压程度;别只暴露“平均耗时”这种模糊指标
goroutine 泄漏最隐蔽的两个源头
它们从不 panic,也不报错,但会在几小时后让 runtime.NumGoroutine() 持续爬升,pprof 显示一堆卡在 http.Get 或 db.Query 的 goroutine。
-
http.Client忘关resp.Body.Close():底层连接无法复用,client 内部悄悄起新 goroutine 管理连接池,越积越多 - worker 里没监听
ctx.Done():比如用select { case job := 但没加 <code>case ,一旦 context 被 cancel,worker 就永久卡在 channel 接收上
所有带 I/O 的 goroutine,只要没显式处理 ctx.Done() 或关闭资源,就等于埋了定时泄漏炸弹。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










