recover() 在普通函数中调用永远返回 nil,仅在 defer 函数执行且当前 goroutine 正 panic 时有效;子 goroutine panic 无法被主 goroutine recover 捕获;recover 后不应继续操作可能已损坏的状态。

recover() 在普通函数里调用永远返回 nil
这是最常踩的坑:把 recover() 直接写在函数体中,比如 if r := recover(); r != nil { ... },它不会捕获任何 panic,始终返回 nil。因为 recover() 只在 defer 函数执行期间、且当前 goroutine 正处于 panic 状态时才有效。
- 错误写法:
func f() { recover(); panic("x") }——recover()执行时 panic 尚未发生,也非 defer 内,必然无效 - 正确写法必须是:
defer func() { if r := recover(); r != nil { ... } }() - 注意:不能写成
defer recover()—— 这会在注册 defer 时就立即执行recover(),此时还没 panic,返回nil,且后续 panic 无人捕获
子 goroutine panic 无法被主 goroutine 的 recover 捕获
每个 goroutine 的 panic 是隔离的。主线程里加了 defer+recover(),对 go func() { panic("oops") }() 完全没反应,程序照样崩溃退出。
- 典型翻车场景:启动 worker 协程处理任务,主函数只在 main 里包了一层
defer recover(),结果 worker panic 后进程直接挂掉 - 正确做法:每个可能 panic 的 goroutine 都要自己配
defer func() { recover() }() - 可封装为工具函数:
safeGo(func() { ... }),内部启动 goroutine 并自动包裹 recover - HTTP handler 中同理:Gin 默认有 recover 中间件,但自定义 handler 若没开中间件,就得自己加 defer
recover 后继续执行危险代码导致二次 panic
recover() 只让 goroutine 软着陆,不修复已破坏的状态。数组越界后 recover 成功了,不代表那个 slice 还能安全访问;map 被并发写入损坏后,再读它可能立刻再次 panic。
- 绝对不要在
recover()后续逻辑中:close(ch)(ch 可能已关闭)、向已损坏的 map 写入、解引用刚 panic 过的指针字段 - 推荐模式:
if r := recover(); r != nil { log.Printf("panic: %v", r); return }—— 记录完立刻退出函数 - 资源清理应放在 defer 链里(如
defer fclose(f)),而非recover()后的手动逻辑中 - 类型断言前必须先判空:
r != nil,否则r.(error).Error()会因r是 string 而再次 panic
想打印完整堆栈却只拿到 panic 值
recover() 只返回 panic 的值(如 "index out of range"),不带位置信息。要看到哪一行触发的 panic,必须配合 runtime/debug.Stack()。
-
debug.Stack()返回[]byte,需转成string()才能打印或记录 - 必须在
recover()后**立即调用**,否则后续 defer 执行可能改变栈帧,拿到的不是原始 panic 栈 - 生产环境建议截断长度(如
string(debug.Stack()[:min(len(debug.Stack()), 4096)])),避免日志爆炸 - 别用
fmt.PrintStack()—— 它直接输出到os.Stderr,不可控,也不方便集成进结构化日志
真正难的不是写对那一行 defer func() { recover() }(),而是判断该不该 recover、在哪一层 recover、recover 后敢不敢碰那些变量。状态一旦破坏,recover() 只是给你一个体面退出的机会,不是 rewind 键。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











