recover必须在defer中以匿名函数形式调用才有效,且仅对同goroutine内的panic生效;goSafe等封装需在新协程内注册defer;recover后不应继续使用可能损坏的状态,应记录堆栈并终止或重抛;生产环境需限制debug.Stack()长度以防日志爆炸。

recover必须在defer里调用,否则返回nil
直接写recover()没用,哪怕它在panic之前——它只在defer函数执行期间、且panic正在传播时才有效。常见错误是把recover()塞进普通if块、提前return分支,或者写成defer recover()(这会在注册时就执行,此时还没panic)。真正有效的形式只能是:defer func() { if r := recover(); r != nil { /* 处理 */ } }()。
通用安全调用函数要处理goroutine隔离问题
Go的panic不跨goroutine,主线程里包一层recover()对子协程完全无效。所以封装goSafe()这类函数时,必须在新goroutine内部注册defer:
func goSafe(f func()) {
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("goroutine panic: %v", r)
}
}()
f()
}()
}使用时:goSafe(func() { panic("boom") })。注意:不能写成goSafe(someFunc)后在someFunc里再defer——那已经晚了,panic发生在someFunc内部,但recover注册在外部goroutine里。
recover后不能继续用已破坏的状态
捕获到panic只是让函数不崩溃,不代表数据还安全。比如slice越界后recover,那个slice可能已处于未定义行为;map被并发写坏后recover,再读它大概率二次panic。因此通用函数里别试图“继续执行”,推荐模式是:
立即学习“go语言免费学习笔记(深入)”;
- 记录panic值和堆栈(用
debug.Stack(),不是fmt.PrintStack()) - 立即return或显式panic新错误(如
panic(fmt.Sprintf("wrapped: %v", r))) - 避免在recover块里调用close(
ch)、向已close的channel写入、访问可能为nil的指针字段
生产环境建议加堆栈长度限制
debug.Stack()返回完整调用栈,但可能长达数MB,尤其嵌套深或有大量中间件时。日志系统容易被撑爆。建议截断:
stack := debug.Stack()
if len(stack) > 4096 {
stack = stack[:4096]
}
log.Printf("panic: %v\n%s", r, string(stack))这个4KB阈值在多数服务中够用,开发期可放开。另外,recover()返回的是interface{},别直接r.(error).Error()——类型断言失败会再次panic,先用fmt.Sprintf("%v", r)最稳妥。
真正难的不是写对recover,而是判断该不该用:99%的panic本该用error返回,而不是靠recover兜底。它只适合那些你完全无法预判、又必须防止进程退出的边界场景,比如HTTP handler入口、定时任务主循环、第三方库调用包裹层。


















