runtime.Stack 是获取当前 goroutine 堆栈最直接轻量的方式,需传 false 且预分配足够 buf(如 64KB),避免截断或性能损耗;all=true 会 STW 且开销巨大,生产环境禁用。

Go 语言里 runtime.Stack 是最直接、最轻量的方式获取当前 goroutine 的堆栈,但它默认只输出到 []byte,且不包含符号信息(比如函数名)——除非你传 true 给第二个参数。
用 runtime.Stack 获取当前 goroutine 堆栈
这是最常用也最稳妥的做法。注意它有两个参数:buf(目标缓冲区)和 all(是否抓所有 goroutine)。要抓当前协程,必须传 false;否则你会拿到整个程序所有 goroutine 的堆栈,开销大、内容杂。
-
buf需预先分配足够空间,比如make([]byte, 1024*10);太小会截断,太大浪费内存 - 返回值是实际写入长度,建议用
string(buf[:n])转成可读字符串 - 如果只关心调用链(不关心 goroutine 状态),
all=false是唯一合理选择
buf := make([]byte, 1024*10)
n := runtime.Stack(buf, false)
fmt.Printf("stack:\n%s", string(buf[:n]))
为什么不用 debug.PrintStack()?
它本质就是调用 runtime.Stack(os.Stderr, true),固定输出到标准错误、且强制抓全部 goroutine。在生产环境或日志采集场景下,这既不可控又低效。
- 无法捕获返回值,只能打印,没法做格式化或上报
-
all=true会导致堆栈巨大,尤其高并发服务里可能几百 MB - 没有缓冲控制,容易阻塞或触发 GC 压力
想看函数名和行号?得配 go build -ldflags="-s -w" 吗?
完全相反:加 -s -w 会删掉符号表,导致 runtime.Stack 输出里函数名变成 ??? 或地址。要让堆栈带可读函数名和行号,构建时**不能**加这两个 flag。
- 默认构建(无
-ldflags)就自带调试符号,runtime.Stack可正常解析 - 若已用
-s -w构建,堆栈里只剩地址,需配合addr2line手动查,但线上几乎不可行 - CI/CD 流水线中建议区分 debug 和 release 构建:debug 版保留符号,用于日志诊断
生产环境慎用:堆栈采集有性能代价
每次调用 runtime.Stack 都要遍历当前 goroutine 的栈帧、格式化字符串、分配内存。高频调用(比如每毫秒一次)会显著拖慢吞吐、增加 GC 压力。
- 仅在异常路径(如 panic 捕获、超时告警)中调用,避免循环或热点路径
- 若需定期采样,建议用
runtime.GoroutineProfile替代,它更轻量且支持增量 - 注意
buf复用:可把缓冲区做成 sync.Pool,避免频繁 malloc
真正难的不是怎么取堆栈,而是判断“该不该取”——太多人把它当调试开关开着上线,结果发现 CPU 突然涨了 20%。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











