pprof 默认启用但不暴露 goroutine profile,需显式导入 _ "net/http/pprof" 并确保 handler 正确注册到 HTTP mux;访问 /debug/pprof/goroutine?debug=1 获取全量栈(查泄漏),?debug=2 仅返回 runnable/running 状态(轻量定位阻塞);分析时必须加 -symbolize=none 参数避免符号化失败。

pprof 默认不暴露 goroutine 调用栈,得手动注册
Go 的 net/http/pprof 默认只开启 /debug/pprof/profile(CPU)、/debug/pprof/heap 等几个端点,/debug/pprof/goroutine 是**默认启用但不对外暴露**的——它需要显式导入并注册 handler,否则直接访问会返回 404。
常见错误现象:curl http://localhost:6060/debug/pprof/goroutine 返回 404 或 “not found”,不是服务没起,是 handler 没挂上。
- 在 HTTP server 启动前,加一行:
import _ "net/http/pprof"(注意下划线导入) - 确保 mux 路由没覆盖
/debug/...路径;如果用了自定义 mux(如 gorilla/mux、chi),需显式调用http.Handle("/debug/", http.DefaultServeMux)或把 pprof 子路由挂到你的 router 上 - 若用
http.ServeMux以外的框架(如 Gin),要手动注册:r.GET("/debug/pprof/*pprof", gin.WrapH(http.DefaultServeMux))
goroutine 采样模式分两种:all 和 running
/debug/pprof/goroutine?debug=1 输出所有 goroutine 的栈(包括休眠中、channel 阻塞、syscall 等),而 ?debug=2 只输出正在运行或可运行(runnable)的状态——后者更轻量,适合高并发场景快速定位“真正在干活”的 goroutine。
容易踩的坑:线上 dump ?debug=1 可能瞬间生成几十 MB 文本(尤其有成千上万 idle goroutine 时),拖慢响应甚至 OOM;调试阻塞问题优先用 ?debug=2。
立即学习“go语言免费学习笔记(深入)”;
-
?debug=1:全量栈,适合查泄漏(比如忘记 close channel 导致 goroutine 积压) -
?debug=2:仅 runnable + running,适合查 CPU 占用高、死锁、channel 卡住 - 不带
?debug参数时,返回的是 gzipped 的二进制 profile(可被go tool pprof解析),不是文本栈
用 go tool pprof 分析 goroutine profile 要加 -symbolize=none
直接 go tool pprof http://localhost:6060/debug/pprof/goroutine 会卡住或报错:failed to fetch symbolized profile。因为 goroutine profile 是堆栈快照,不含符号信息,pprof 默认尝试远程符号化,失败后不降级。
正确做法是关掉符号化,并指定输出格式:
- 看文本栈:
go tool pprof -symbolize=none -http=:8080 http://localhost:6060/debug/pprof/goroutine - 导出纯文本:
go tool pprof -symbolize=none -text http://localhost:6060/debug/pprof/goroutine - 生成火焰图:
go tool pprof -symbolize=none -svg http://localhost:6060/debug/pprof/goroutine > goroutines.svg
注意:-symbolize=none 不是可选参数,是必须项;漏掉就白等半分钟然后报错退出。
goroutine 泄漏最典型的 pattern 是 select + default
很多开发者用 select { case 轮询 channel,但忘了加 context 或超时控制,导致 goroutine 无法退出。pprof 查到这类泄漏时,栈顶往往停在 <code>runtime.gopark 或 time.Sleep,中间夹着你自己的函数名。
实操建议:
- 用
?debug=1抓两次快照(间隔 30 秒),diff 文本找持续存在的 goroutine 栈 - 重点关注重复出现、栈深度浅、停留在 sleep/select/default 的 goroutine
- 检查是否漏了
ctx.Done()监听,或 channel 发送方已关闭但接收方还在循环
goroutine profile 本身不带时间戳和生命周期信息,得靠人工比对两次 dump —— 这个细节容易被忽略,一上来就盯着单次结果找“异常”,反而错过增长趋势。


















