runtime.stack 默认只抓当前 goroutine 堆栈,因第二个参数 all=false;要获取全部需显式传 true,但会触发 stw 且开销大,生产环境应优先使用 pprof/goroutine 接口。

runtime.Stack 是 Go 运行时提供的轻量级堆栈捕获工具,但它**默认只抓当前 goroutine 的堆栈**,且输出是字节切片,不带符号解析(比如函数名被截断、无文件行号)。直接用它做调试或监控,很容易拿到一堆难以解读的地址信息。
为什么 runtime.Stack 抓不到所有 goroutine?
因为它的第一个参数 buf 是输出缓冲区,第二个参数 all 才决定范围:
-
all = false:只写入当前 goroutine 堆栈(最常用,开销小) -
all = true:写入所有 goroutine 堆栈(阻塞式,GC 会暂停,生产环境慎用)
很多人传了 nil 或没传第二个参数,结果默认走 false,误以为“没抓到其他协程”。另外,buf 太小会导致截断——常见错误是传 make([]byte, 64),但实际堆栈常超几 KB。
怎么安全地获取当前 goroutine 的完整堆栈?
关键在缓冲区大小和解码方式。不要自己拼接字符串,用 debug.PrintStack() 更省心;若需字符串处理,按如下方式:
buf := make([]byte, 1024*10) // 至少 10KB n := runtime.Stack(buf, false) stack := string(buf[:n])
注意:n 是实际写入长度,必须用 buf[:n],否则可能包含旧数据;10KB 覆盖绝大多数单 goroutine 堆栈,但递归过深或调用链极长时仍可能不够。
如何避免 runtime.Stack(buf, true) 拖垮服务?
抓全量堆栈会触发 STW(Stop-The-World),尤其在高并发 goroutine 场景下,延迟可达毫秒级甚至更久。生产环境应规避以下操作:
- 在 HTTP handler 或关键路径中直接调用
runtime.Stack(buf, true) - 未设超时或限频就暴露为 debug 接口(如
/debug/pprof/goroutine?debug=2已更优) - 把结果直接
log.Printf—— 字符串化+打印本身也耗 CPU,建议先采样再输出
替代方案:用 net/http/pprof 的 /debug/pprof/goroutine?debug=2,它内部做了流式输出和缓冲控制,更健壮。
为什么堆栈里看不到函数名或行号?
Go 编译时若加了 -ldflags="-s -w"(去符号表 + 去调试信息),runtime.Stack 就只能显示类似 main.main.func1(0x456789) 的地址形式。这不是 API 问题,而是二进制缺失元数据。
开发/测试环境务必保留调试信息:
go build -o app . # 不加 -s -w # 或显式保留: go build -ldflags="-w" -o app . # 只去 DWARF,留符号表
另外,交叉编译或 UPX 打包也会破坏符号,这类构建产物不适合用 runtime.Stack 做深度诊断。
真正难的是权衡:要堆栈完整性就得接受 STW 风险,要低开销就得接受信息残缺。多数时候,结合 pprof 接口 + 有符号的 release 构建 + 关键点打点,比硬啃 runtime.Stack 输出更可靠。











