go tool trace是Go运行时注入的用户态调度追踪器,不拦截系统调用,仅适用于“卡但没panic”场景;需显式导入_net/http/pprof_并立即启动HTTP服务,避免端口占用或路径污染,且须用-duration=20s等参数延长默认5秒采集窗口以捕获高并发死锁。
go tool trace 不是 ptrace,它根本不会拦截系统调用,也查不到 linux 层面的阻塞;名字带 “ptrace” 是常见误搜,实际它是 go 运行时注入的用户态调度追踪器,只适用于“卡但没 panic”的场景。
go tool trace 启动失败:connection refused
报错 failed to fetch trace: Get "http://": dial tcp : connect: connection refused,说明 /debug/trace 接口压根没暴露出来。
- 必须显式 import
_ "net/http/pprof",光 import 不启动 HTTP server 没用 -
http.ListenAndServe("localhost:6060", nil)要在main()开头就起 goroutine 启动,别等业务逻辑跑完才开 - 端口被占时
ListenAndServe会静默失败——加一行log.Printf("pprof server started on :6060")确认是否真起来了 - 别往
http.DefaultServeMux注册其他 handler,/debug/*路径必须干净
trace.out 里看不到阻塞 goroutine
默认只采前 5 秒,而高并发死锁常发生在第 8–12 秒——你打开 UI 时程序早已卡死,trace 文件里全是 running 或 runnable,根本没 blocked 状态。
- 用
-duration=20s显式延长采集窗口:go tool trace -duration=20s http://localhost:6060/debug/trace - 如果程序启动即卡死(比如 main goroutine 在第一行就
ch ),加 <code>time.Sleep(1 * time.Second)确保 pprof server 先就绪 - Web UI 中进 “Goroutine” 标签页,筛选状态为
chan send、chan receive或SyncBlock,点进去看具体卡在哪行ch 或 <code>
看到 goroutine 卡在 (*Mutex).Lock 但没 panic
这说明不是运行时检测到的“全 goroutine 都 asleep”,而是单个 goroutine 自己把自己锁死了——比如持有 mu 时又调了同一把锁的 mu.Lock(),或 defer 里没执行 mu.Unlock() 导致锁一直被占。
-
go tool trace能标出谁在等锁,但看不出是谁持着不放;得配合curl http://localhost:6060/debug/pprof/goroutine?debug=2查全部堆栈,找有没有 goroutine 卡在(*Mutex).Unlock或刚进(*Mutex).Lock就停住 -
sync.RWMutex更危险:同一个 goroutine 多次RLock()或混用Lock()/RLock(),会导致内部计数器错乱,后续所有Lock()永远阻塞 - 无缓冲 channel 的阻塞点最明显;有缓冲的要结合
len(ch)和cap(ch)判断是“真卡死”还是“暂时满/空”
真正卡死并报 fatal error: all goroutines are asleep - deadlock! 时,go tool trace 已经来不及介入——panic 后进程立刻终止,HTTP server 来不及响应。这时候唯一可信的是 panic 日志末尾打印的全部 goroutine 堆栈,别跳过去看 trace。


















