Go中panic无法被error捕获,必须在同goroutine的defer中用recover截获;recover仅返回panic值,需配合debug.Stack()获取堆栈;类型判断与业务逻辑决定是否继续运行。

Go中Panic无法被普通error捕获,必须用recover
Go的panic不是error,它会立即中断当前goroutine的执行流,跳过所有defer之外的代码。你写if err != nil完全抓不到它——这是新手最常踩的坑。只有在defer函数里调用recover()才可能截住它,而且必须在panic发生**同一goroutine**中、且recover()得在panic之后、函数返回之前执行。
recover必须放在defer里,且不能跨goroutine生效
常见错误是把recover写在主函数开头、或另起一个goroutine去监听。它只对当前goroutine有效。正确做法是:在可能触发panic的代码外层加defer func(),并在其中调用recover():
func safeCall(f func()) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("panic caught: %v\n", r)
}
}()
f()
}
调用时:safeCall(func() { someThirdPartyLib.DoSomething() })。注意:如果第三方库自己启了新goroutine并panic,这个recover无效——那是另一个goroutine,得在那个goroutine内部做recover。
打印完整堆栈需用debug.PrintStack或runtime/debug.Stack()
仅recover()返回的是panic值(比如"index out of range"),不带位置信息。要看到哪一行出的问题,得额外捕获堆栈:
立即学习“go语言免费学习笔记(深入)”;
-
debug.PrintStack()直接输出到stderr,适合调试时快速看 -
stack := debug.Stack()返回[]byte,可转成string后和panic值一起记录 - 更健壮的做法是组合使用:
r := recover(); fmt.Printf("panic: %v\n%s", r, debug.Stack())
注意:runtime/debug.Stack()开销略大,生产环境高频调用需权衡;debug.PrintStack()不可重定向,日志系统集成时不如前者灵活。
第三方库panic类型多样,recover后需判断具体值再处理
有些库panic的是字符串,有些是自定义error类型(比如fmt.Errorf),甚至可能是结构体。直接fmt.Printf("%v", r)有时看不出端倪。建议:
- 先用
fmt.Printf("panic type: %T, value: %+v\n", r, r)确认类型 - 若为
error接口,可尝试类型断言:if err, ok := r.(error); ok { log.Println("err:", err.Error()) } - 避免盲目
os.Exit(1)——recover后程序还能继续,是否终止取决于业务逻辑
真正难的不是捕获,而是判断这个panic是否真的可恢复:比如数据库连接池耗尽导致的panic,重试可能有用;而内存溢出类panic,recover后继续运行风险很大。



















