需调用 runtime.setblockprofilerate(n) 降低采样阈值(如设为100000纳秒),并确保服务有真实阻塞压力、采集时间≥10秒,同时确认请求路由正确到达 /debug/pprof/block。

怎么开启 block profile 并拿到有效数据
go 的 runtime/pprof 提供了 block profile,但它默认关闭,且只记录**阻塞时间超过 1ms 的事件**(注意:不是所有 goroutine 都会被采样,而是阻塞动作本身被采样)。不主动启用,go tool pprof 就读不到任何 block 数据。
- 启动时加
-blockprofile=block.out参数(仅限go run或二进制运行);更常用的是在代码里调用pprof.StartCPUProfile之类的方式——但 block profile 不支持 Start/Stop 控制,必须靠持续运行+信号触发或定时写入 - 服务类程序建议在 http handler 中暴露
/debug/pprof/block(需导入_ "net/http/pprof"),然后用wget http://localhost:8080/debug/pprof/block?seconds=30抓取 30 秒内的阻塞事件 - 注意:
?seconds=1很可能抓不到数据——因为默认采样阈值是 1ms,而短时间低频阻塞容易被漏掉;建议至少 10 秒以上,且系统得有真实阻塞压力
看到 pprof 输出里全是 runtime.semasleep、runtime.notesleep 怎么办
这类函数名说明你正卡在底层同步原语上,不是业务逻辑问题,而是 goroutine 在等锁、channel、timer 或 sync.Cond。关键不是“它在哪睡”,而是“它为什么醒不了”。
-
runtime.semasleep通常对应 channel send/recv 阻塞、sync.Mutex.Lock等待、或time.Sleep—— 检查是否有 goroutine 持有锁过久,或往无缓冲 channel 发送后没人接收 -
runtime.notesleep多见于sync.Once.Do、sync.WaitGroup.Wait或自定义的runtime.Note等待,重点看谁没被 signal / done - 别只盯着 top 函数,用
pprof -http=:8081 block.out打开火焰图,点开高耗时路径,看调用链最上层是不是你的业务函数(比如handleOrder→db.QueryRow→conn.exec→ channel recv)
block profile 和 mutex profile 容易混淆,怎么区分它们的用途
两者都跟“等待”有关,但监控对象完全不同:block profile 记录**所有因同步原语导致的阻塞**(channel、锁、WaitGroup、timer),mutex profile 只记录**锁竞争本身**(即 sync.Mutex.Lock 被阻塞的时间,且只对标准库 sync.Mutex 和 sync.RWMutex 生效)。
- 如果你怀疑是锁拖慢了整体吞吐,优先看 mutex profile;如果整个服务响应变慢、goroutine 数暴涨但 CPU 不高,block profile 更有用
- mutex profile 默认关闭,需设置
GODEBUG=mutexprofile=1或代码中调用pprof.Lookup("mutex").WriteTo - block profile 的采样开销比 mutex profile 高得多——频繁阻塞会显著增加 runtime 调度负担,生产环境开启需谨慎,建议只在问题复现窗口临时启用
为什么本地能复现,线上 pprof 却几乎没数据
常见原因不是 profile 没开,而是阻塞事件太短或太分散,被默认 1ms 采样阈值过滤掉了。
立即学习“go语言免费学习笔记(深入)”;
- Go 1.20+ 允许通过
runtime.SetBlockProfileRate(n)动态调低阈值(单位纳秒),比如设成100000(100μs)可捕获更多细粒度阻塞;但注意:设太低会让调度器频繁中断,影响性能 - 线上服务常启用了
GOMAXPROCS限制,若阻塞集中在某几个 P 上,其他 P 没压测流量,整体 profile 就显得“安静”——建议结合go tool trace查看 goroutine 状态分布 - HTTP handler 的
/debug/pprof/block是按请求采集,若你用curl直连但服务走反向代理,可能请求根本没落到 profile handler 上;确认路由和中间件是否透传了该路径
block profile 的真正难点从来不在怎么开,而在于如何把“goroutine 在等什么”映射回你写的那行 ch 或 <code>mu.Lock() —— 中间隔着 runtime、net、database 多层封装,得一层层剥开调用栈,而不是只信 top 函数名。