goroutine泄漏指本该退出的goroutine持续运行,导致其持有变量无法被GC回收;表现为NumGoroutine()持续上涨,pprof显示大量goroutine阻塞在channel、锁或IO等操作上。

goroutine 泄漏:最隐蔽也最常被忽略
goroutine 不退出,它持有的所有变量(包括闭包捕获、channel 缓冲区、参数对象)就一直无法被 GC 回收。这不是“内存分配多”,而是“本该死的没死”。
常见现象:runtime.NumGoroutine() 持续上涨;pprof 的 /debug/pprof/goroutine?debug=2 显示大量阻塞在 或 select{} 的 goroutine。
- 错误写法:
go func() { for { doWork() } }()—— 没退出条件,永不结束 - 正确做法:必须用
context.Context控制生命周期,select中监听ctx.Done(),并在退出前cancel() - 容易踩的坑:忘记
defer cancel();或在 goroutine 内部调用cancel()导致其他协程提前退出 - 测试时可用
goleak工具自动检测:在TestMain中调用leakcheck.VerifyTestMain(m)
全局缓存无限增长:你以为是优化,其实是泄漏
把 map[string]interface{} 或 sync.Map 当成万能缓存,不设大小限制、不加过期、不淘汰,就是典型的“自我积累型泄漏”。
典型场景:HTTP 服务中用全局 map 存用户 session、API 响应结果、结构体反射字段(如 copier 的 deepFieldsMap)。
- 错误写法:
var cache = make(map[string]string)+cache[key] = value无任何清理逻辑 - 修复方向:改用带 TTL 的 LRU(如
github.com/hashicorp/golang-lru),或用time.AfterFunc配合sync.Map定时清理 - 关键检查点:pprof
top -cum看是否大量内存来自runtime.makeslice或runtime.mapassign,再结合源码定位 map 插入点 - 注意:
sync.Map本身不解决容量失控问题,只是并发安全——泄漏根源在业务逻辑,不在同步机制
资源未关闭:文件/连接/定时器,一个不关,全卡住
Go 不会自动帮你关文件、DB 连接、HTTP client transport、time.Timer,只要句柄还开着,底层资源(fd、内存缓冲区、goroutine)就一直占着。
错误示例:file, _ := os.Open("log.txt") 后没 defer file.Close();timer := time.NewTimer(d) 用完没 timer.Stop()。
- 标准姿势:所有实现
io.Closer接口的对象,都应配defer xxx.Close() - 数据库连接要特别小心:用
db.SetMaxOpenConns()+db.SetConnMaxLifetime()防连接池膨胀;查询后记得rows.Close() - HTTP client 泄漏高发:自定义
http.Transport时,若没设MaxIdleConns和IdleConnTimeout,空闲连接会越积越多 - pprof 中识别:看
/debug/pprof/heap是否有大量net.Conn、os.File、time.timer实例
用 pprof 定位泄漏:别只点开网页,要会比、会筛、会读栈
pprof 不是“打开就能看到泄漏”,它是显微镜——你得知道看哪、怎么调参、怎么排除噪音。
实操建议:
- 先启动服务并引入:
import _ "net/http/pprof"+go http.ListenAndServe("localhost:6060", nil) - 采集两次快照(间隔数分钟):
go tool pprof http://localhost:6060/debug/pprof/heap,用-inuse_space(当前占用)和-alloc_space(累计分配)分别分析 - 关键命令:
top -cum看调用链顶端;list funcName定位具体行号;web生成调用图(需安装 graphviz) - 避坑提醒:默认看的是
-inuse_space,但某些泄漏(如 goroutine 持有大 slice)在-alloc_space下更明显;别忽略/debug/pprof/goroutine?debug=2的文本堆栈 - 生产环境慎用:可配合
GODEBUG=gctrace=1观察heap_live是否持续上涨,再决定是否开 pprof
runtime.mallocgc 调用次数每秒稳定上升,且 Alloc 每分钟涨 50MB 不回落,那基本可以动手查了。










