Go goroutineleak profile 怎样接入 pprof 诊断
来源:17golang原创
时间:2026-11-13 21:32:38 137浏览 收藏
如果 Go 服务已经接入 net/http/pprof,接入 goroutineleak 通常不需要再写一个新的 HTTP handler:直接访问 /debug/pprof/goroutineleak 即可拿到泄漏 goroutine 的 profile,再交给 go tool pprof 按栈聚合定位阻塞点。若服务还没有 pprof,则先安装默认 mux handler 或在自己的 mux 上注册对应 handler。
官方地址:https://go.dev/blog/goroutine-leak-profiles
这套 profile 和普通的 goroutine profile 不是一回事:前者筛选被运行时判定为泄漏的 goroutine,后者列出当前进程的全部 goroutine 栈。诊断时先抓对入口,再把结果解释为“某个阻塞生命周期的线索”,不要把一次空结果当成绝对没有并发问题。
先分清两个 profile:总量快照和泄漏筛选
runtime/pprof 提供名为 goroutine 和 goroutineleak 的预定义 profile。goroutine 适合回答“当前有哪些 goroutine、它们停在哪个栈”;goroutineleak 进一步筛选被判定为泄漏的 goroutine,适合回答“哪些阻塞状态不再有可达的解除路径”。
| 入口 | 主要回答的问题 | 解读重点 |
|---|---|---|
/debug/pprof/goroutine | 当前所有 goroutine 的栈是什么 | 状态、重复栈、总量和阻塞位置 |
/debug/pprof/goroutineleak | 哪些 goroutine 被识别为泄漏 | 阻塞原语与无法完成的生命周期 |
/debug/pprof/heap | 哪些对象仍占用堆 | 它不是 goroutine 泄漏判定入口 |
因此,看到 goroutine 数上涨时,先用普通 profile 了解全貌,再用 goroutineleak 缩小范围。profile 只描述采样时刻的运行状态,真正的修复仍要回到创建 goroutine 的代码,检查 channel、select、锁和退出条件。
接入入口:让 pprof 暴露 goroutineleak
最省事的方式是匿名导入 net/http/pprof,并让默认 mux 处理诊断请求。官方包会把一组 profile handler 注册到 /debug/pprof/;如果服务使用自定义 mux,则必须显式注册 handler,不能只保留匿名导入。
package main
import (
"log"
"net/http"
// 匿名导入会把 pprof handler 注册到 DefaultServeMux。
_ "net/http/pprof"
)
func main() {
// 诊断端口应放在受控网络边界内,不要直接暴露给不可信流量。
log.Fatal(http.ListenAndServe("127.0.0.1:6060", nil))
}
当服务启动后,入口就是 http://127.0.0.1:6060/debug/pprof/goroutineleak。文章中的地址只表示本地示例;生产环境应通过内网、访问控制或运维代理保护 pprof,避免把诊断栈暴露给无关调用方。

自定义 mux 的场景可以直接使用 pprof.Handler 返回的 handler。这样做的好处是能把诊断路由放到单独的监听器或独立路径,避免和业务 mux 的权限策略混在一起。
package main
import (
"log"
"net/http"
"net/http/pprof"
)
func main() {
mux := http.NewServeMux()
// 只把 goroutineleak 诊断 handler 挂到受控的自定义路径。
mux.Handle("/internal/pprof/goroutineleak", pprof.Handler("goroutineleak"))
// 监听地址仍然限制在本机,外部访问由受控代理转发。
log.Fatal(http.ListenAndServe("127.0.0.1:6060", mux))
}
若自定义 mux 上出现 404,优先检查是否注册了 pprof.Handler("goroutineleak"),以及请求路径是否和注册路径完全一致。不要因为首页 /debug/pprof/ 可访问,就推断自定义路由也已经注册。
抓取 profile:先保存,再交给 go tool pprof
抓取时可以先把响应保存为本地 profile 文件,再让 pprof 工具读取。这样既方便重复查看,也避免每次排查都重新触碰线上诊断端点。
# 保存 goroutineleak 快照;这里不把输出当成业务日志。 curl --fail --silent --show-error \ 'http://127.0.0.1:6060/debug/pprof/goroutineleak' \ --output leak.prof # 打开交互式 pprof;可在其中使用 top、list 和 web 等命令。 go tool pprof leak.prof
在交互界面中,top 先给出聚合后的栈,list 函数名 再把热点映射回源码行。官方示例中,泄漏栈最终落在向 channel 发送结果的位置;这类结果的价值在于指出“哪一个阻塞操作长期无法完成”,而不是替你证明唯一根因。
# 下面是 pprof 交互命令,不是程序运行日志。 (pprof) top (pprof) list processWorkItems (pprof) quit
重复采样:不要把 seconds=N 误当成 goroutineleak 必需参数
goroutineleak 更适合按时间点抓取快照。为了判断泄漏是否持续,可以在问题复现前后各保存一个 profile,再比较聚合栈和数量变化。net/http/pprof 文档中的 seconds=N 参数主要用于普通 goroutine、heap、block 等 profile 的差值采样;不要把它机械地加到 goroutineleak URL 上并据此解释结果。
# 记录问题出现前的泄漏快照;文件名包含时间点方便人工对照。 curl --fail --silent 'http://127.0.0.1:6060/debug/pprof/goroutineleak' \ --output leak-before.prof # 复现一次请求或任务后,再记录第二个泄漏快照。 curl --fail --silent 'http://127.0.0.1:6060/debug/pprof/goroutineleak' \ --output leak-after.prof # 如需观察所有 goroutine 的差值,再单独使用普通 goroutine profile。 curl --fail --silent \ 'http://127.0.0.1:6060/debug/pprof/goroutine?seconds=5' \ --output goroutine-delta.prof

比较时重点看三件事:同一个栈是否在第二次 profile 中继续出现,聚合数量是否随复现次数增长,以及阻塞位置是否都落在同一个生命周期边界。普通 profile 的差值能补充“总量如何变化”,但它不替代 goroutineleak 的筛选语义。
结果解读:从阻塞原语回到退出路径
拿到一组泄漏栈后,按阻塞原语反查代码通常比盯着 goroutine 数更有效。常见线索包括:发送方等待没人接收的 channel、接收方等待永远不会关闭的 channel、没有取消分支的阻塞 select、无人释放的 Mutex,以及等待条件永远无法满足的 WaitGroup 或 Cond。
func process(items []int) error {
results := make(chan int)
for _, item := range items {
go func(v int) {
// 发送可能永久阻塞;调用方提前返回时要有可完成的路径。
results
修复方向不是“把 goroutine 数量压低”,而是让每个创建出来的 goroutine 都有明确的完成条件:发送有接收者或取消分支,worker 能看到关闭信号,锁有对应的释放路径,等待组的计数和 Done 调用成对。修复后重新抓取 profile,观察原有聚合栈是否消失或不再增长。
检测边界:空结果、漏报和开销都要有解释
官方实现把 goroutine 是否存活与它阻塞的并发原语是否仍被可达的存活 goroutine 持有联系起来,因此它不是任意“卡住”的 goroutine 过滤器。文件或网络 I/O、直接系统调用,以及不依赖标准并发原语的自定义自旋锁,不会自动变成 goroutineleak 的报告对象。
- 空 profile 只表示当前采样没有识别到符合条件的泄漏,不等于没有慢任务、死锁或资源泄漏。
- 如果并发原语一直能从全局变量或可运行 goroutine 到达,运行时可能无法把相关阻塞 goroutine 判为泄漏。
- 偶发问题必须在实际泄漏已经发生后抓取,单次未复现的采样不能证明问题不存在。
- 泄漏检测会增加 GC 的工作量;应把采样频率、服务负载和诊断收益一起评估。
线上使用时,优先在受控副本上采样,并让一次诊断只收集一个主要 profile。Go 官方诊断文档也提醒,不同 profile 可能相互干扰;收集结果时要把采样时间、服务实例和复现动作记在排查记录中。
落地清单:从入口到修复的六个检查点
- 确认运行时和工具链支持
goroutineleak,不要把旧环境的普通goroutine入口当成同一能力。 - 默认 mux 使用匿名导入,自定义 mux 使用
pprof.Handler("goroutineleak")显式注册。 - 先用 curl 保存 profile,再用
go tool pprof做top和list栈聚合。 - 需要比较趋势时按时间点保存多个泄漏快照;需要普通 goroutine 差值时单独使用
goroutine?seconds=N。 - 沿着聚合栈回查 channel、select、锁和等待组的生命周期,不要只根据数量变化猜根因。
- 修复退出、关闭、取消或释放路径后重新采样,并把空结果、漏报边界和 GC 成本记录清楚。
可以把这篇文章的核心顺序记成一句话:先接入 goroutineleak,再保存快照;先看阻塞栈,再修生命周期。
相关问题
已经接入 net/http/pprof,还要单独注册 goroutineleak 吗?
使用默认 mux 时通常不需要,profile handler 会自动提供对应入口;如果服务使用自定义 mux,则要显式注册 pprof.Handler("goroutineleak")。
goroutineleak 和 goroutine profile 能互相替代吗?
不能。普通 profile 展示当前全部 goroutine,goroutineleak 只筛选被判定为泄漏的 goroutine;前者适合全貌,后者适合缩小泄漏范围。
为什么抓到空的 goroutineleak profile?
可能是泄漏尚未发生,也可能是阻塞原因不在支持的并发原语范围内。还要考虑内存可达性和问题的非确定性,不能只凭一次空快照下结论。
应该给 goroutineleak URL 加 seconds 参数吗?
不要把 seconds=N 当成它的必需参数。goroutineleak 先按快照采集;如果要做普通 goroutine 的差值采样,再使用 /debug/pprof/goroutine?seconds=N 作为补充。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
Golang · Go教程 | 4分钟前 | 标准库 · pprof · Go教程 · Go Debug pprof 性能诊断 runtime/pprof goroutine profile Profile.WriteTo224 收藏
-
295 收藏
-
101 收藏
-
373 收藏
-
Golang · Go教程 | 12分钟前 | 标准库 · JSON · 错误处理 · 数据校验 · Go教程 · Go IsValid encoding/json/jsontext jsontext.Value JSON片段 WriteValue280 收藏
-
143 收藏
-
Golang · Go教程 | 16分钟前 | 标准库 · JSON · 错误处理 · Go教程 · 接口设计 · Go 结构体标签 encoding/json/v2 RejectUnknownMembers JSON严格解码418 收藏
-
Golang · Go教程 | 24分钟前 | 连接池 · Go教程 · database/sql · Go 连接池 database/sql DBStats sql.DB SetMaxIdleConns293 收藏
-
306 收藏
-
Golang · Go教程 | 1天前 | 依赖管理 · Go教程 · 工程实践 · Go vendor 离线构建 Go Modules go mod vendor modules.txt 依赖一致性132 收藏
-
124 收藏
-
338 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习