go tool trace不是ptrace,它不拦截系统调用,而是通过go运行时注入goroutine调度等事件进行用户态追踪,依赖pprof http接口采集数据。

go tool trace 不能叫 ptrace,它不抓系统调用
很多人搜“ptrace”是冲着 Linux 的 ptrace 系统调用去的,但 Go 自带的 go tool trace 完全无关——它不侵入内核、不拦截系统调用,而是通过 runtime 注入事件(如 goroutine start/block/chan send/recv)来采样。名字里带 “trace” 容易误导,实际它是 Go 特有的用户态调度追踪器。
如果你在高并发下看到程序卡住但没 panic,go tool trace 是比看日志更早发现问题的手段;但它**不会告诉你“哪里死锁了”,只会告诉你“谁卡在哪一行、等什么原语”**。
- 它能显示某个 goroutine 长期处于
chan send状态,且 sender 和 receiver 的 goroutine 都没推进 —— 这就是无缓冲 channel 未配对的铁证 - 它能标出 goroutine 卡在
(*Mutex).Lock,同时另一个 goroutine 卡在(*Mutex).Unlock(比如 panic 后 defer 没执行),说明锁没释放 - 它无法识别“逻辑死锁”:比如两个 goroutine 都在等对方发消息,但各自都用了带
default的select,此时它们在跑,只是不干活 ——go tool trace会显示为 runnable,不是 blocked
启动 trace 前必须加 pprof 服务,否则采集失败
go tool trace 依赖运行时暴露的 /debug/trace 接口,而这个接口由 net/http/pprof 包提供。只 import 不启动 HTTP server,go tool trace 会报错 failed to fetch trace: Get "http://...": dial tcp ...: connect: connection refused。
正确做法是在 main() 开头加:
import _ "net/http/pprof"
<p>func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// 其余逻辑...
}</p>
注意两点:
- 端口别被占用,否则
ListenAndServe会静默失败(不 panic,但 trace 采集不到) - 不要用
http.DefaultServeMux注册其他 handler,避免干扰/debug/*路径 - 如果程序启动极快就卡死,加
time.Sleep(2 * time.Second)确保 pprof server 已就绪再触发业务逻辑
trace.out 里看不到死锁?检查采集时间与阻塞点是否匹配
go tool trace 默认只记录启动后前 5 秒,而高并发死锁可能发生在第 8 秒——你打开 trace UI 后看到的全是“空转”,goroutine 状态都是 running 或 runnable,根本没 block。
解决方法是显式延长采集窗口:
go tool trace -http=localhost:8080 -duration=20s ./your-binary
然后访问 http://localhost:8080,点 “View trace”。重点看:
- Goroutines 标签页 → 筛选状态为
Waiting或SyncBlock的 goroutine - 点击具体 goroutine → 查看其 stack trace 最顶上是不是
runtime.gopark,下面是不是chan.send、sync.(*Mutex).Lock或sync.runtime_SemacquireMutex - 如果多个 goroutine 都卡在同一个 channel 操作(比如都停在
ch ),说明接收端没起来或已 panic 退出
缓冲 channel 的陷阱:若 ch := make(chan int, 1),第一次 ch 成功,第二次才阻塞 —— trace 里可能只看到一个 goroutine 卡住,容易误判为“单点故障”,其实是逻辑漏了接收。
pprof/goroutine 比 trace 更快定位死锁源头
当 fatal error: all goroutines are asleep - deadlock! 已经发生,程序已终止,go tool trace 派不上用场。这时最直接的是复现问题并用 curl http://localhost:6060/debug/pprof/goroutine?debug=2 抓当前所有 goroutine 堆栈。
关键看三类线索:
- main goroutine 停在哪?比如卡在
wg.Wait(),就要查有没有 goroutine 漏掉wg.Done() - 所有 goroutine 是否全停在同一种操作?比如全部卡在
,说明 sender 没人调用;全部卡在 <code>mu.Lock(),说明某处拿了锁没放 - 有没有 goroutine 卡在
select {}或for {}?那是典型的“忘了退出条件”,不是资源争用,但 runtime 仍判定为死锁
真正难排查的不是 panic 那一刻,而是那些“没 panic 但卡半天”的情况:比如某个 goroutine 在持有 mutex 时调用了阻塞的 HTTP 请求,导致其他 goroutine 全在等这把锁 —— 这时候 go tool trace 能看到 Lock 时间异常长,pprof/block 会显示大量 goroutine 停在 semacquire,但你得自己顺藤摸到那个 HTTP 调用。











