必须显式调用runtime.setblockprofilerate(1)并导入_ "net/http/pprof",否则/debug/pprof/block返回空;该设置需在main开头、任何goroutine启动前执行,生产环境建议先设1定位问题再调高以降开销。

pprof 默认不采集 channel 阻塞数据,必须手动开启
Go 运行时默认关闭 block profile 采样,所以即使你已经导入了 _ "net/http/pprof" 并访问 /debug/pprof/block,返回的也总是空内容。这不是 GoLand 的问题,而是 Go 本身的默认行为——它怕影响性能,直接关掉了阻塞跟踪。
要让 pprof 能看到 channel send/recv 的阻塞耗时,必须在程序启动早期显式调用:
runtime.SetBlockProfileRate(1)
这个值设为 1 表示「每次阻塞都采样」;设为 0 则完全关闭;设为 100 表示平均每 100 次阻塞采一次(适合压测环境降噪)。生产环境建议先设 1 定位问题,确认后调高以降低开销。
GoLand 启动配置里加 HTTP server 和 debug 端口
GoLand 本身不运行 pprof,它只是帮你启动你的 Go 程序。关键是你得让程序自己暴露 /debug/pprof 接口,并且别被框架路由覆盖。
常见错误是:用了 Gin/Echo 等 Web 框架,但没把 /debug 路由注册到框架的 mux 上,结果访问 localhost:6060/debug/pprof 返回 404。
最稳妥的做法是单独起一个 debug server:
- 在
main()里加一段独立的 HTTP server,比如监听:6060 - 确保只导入
_ "net/http/pprof",不要手动注册 handler(它会自动注册到http.DefaultServeMux) - GoLand Run Configuration 的 Program arguments 不需要额外参数,但 Working directory 要正确,否则 pprof 生成的临时文件路径可能出错
示例片段:
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
用 GoLand Terminal 执行 go tool pprof 分析 block profile
GoLand 自带 Terminal,不用切到系统终端。分析 channel 阻塞的核心命令是:
go tool pprof http://localhost:6060/debug/pprof/block
注意三点:
- 必须等程序已运行、且有实际 channel 阻塞发生(比如 goroutine 卡在
ch 或 <code>)之后再执行,否则 profile 是空的 - 如果服务跑在 Docker 或 Kubernetes 里,得先
kubectl port-forward或用 SSH 端口转发,让本地能访问到那个:6060 - 进 pprof 交互界面后,用
top看阻塞最久的栈,用web生成火焰图——channel 阻塞会明确标出chan send或chan receive调用点
容易忽略的“伪死锁”:goroutine 数量正常但业务卡住
很多同学看到 runtime.NumGoroutine() 没暴涨,就以为没阻塞问题。但 channel 阻塞未必导致 goroutine 数量激增——比如一个后台 worker goroutine 卡在 ,而 main 又在等它退出,这时只有 2 个 goroutine,CPU 接近 0%,但服务已无法响应新请求。
这种场景下,/debug/pprof/goroutine?debug=2 比 block 更快暴露问题:它会列出所有 goroutine 的完整栈,你能一眼看到哪个卡在 channel 收发上,而不用等阻塞累积出采样数据。
真正难缠的是那些阻塞时间短、频率低、但累积起来拖慢整体吞吐的 case——这时候必须靠 SetBlockProfileRate(1) + 持续采样 + 对比不同负载下的 block profile 差异,而不是单次抓一把就下结论。











