recover必须在defer中调用才有效,仅对当前goroutine内已触发但未退出的panic生效,需在可能panic前注册defer,且recover后须手动清理资源防止泄漏。

recover 必须在 defer 中调用才有效
Go 的 recover 不是全局异常捕获机制,它只在当前 goroutine 的 panic 正在被传播、且尚未退出函数时生效。如果没用 defer 包裹,recover 会立刻返回 nil,什么也捞不到。
常见错误是写成这样:
func handle() {
if r := recover(); r != nil { // ❌ 永远不会触发
log.Printf("panic: %v", r)
}
panic("boom")
}
正确姿势是:
- 把
recover放进defer函数里(匿名或具名均可) - 确保该
defer在 panic 发生前已注册(即在可能 panic 的代码之前声明) - 仅对**当前函数内**发生的 panic 生效,不能跨 goroutine 捕获
主 goroutine 崩溃日志要放在 main 函数最外层 defer
想记录整个程序启动后主流程的 panic,必须在 main() 函数第一行就注册 defer,否则一旦初始化失败(比如 flag 解析出错、配置加载 panic),日志根本来不及写。
示例:
func main() {
defer func() {
if r := recover(); r != nil {
log.Printf("[FATAL] main goroutine panicked: %v", r)
log.Printf("[STACK] %s", debug.Stack())
os.Exit(1)
}
}()
// 启动逻辑:config.Load(), http.ListenAndServe()...
}
注意点:
-
debug.Stack()要显式导入"runtime/debug" -
os.Exit(1)很关键 —— 不加的话,recover 后程序继续执行,可能引发二次 panic 或状态不一致 - 不要依赖
log.Fatal替代os.Exit,因为log.Fatal本身会调用os.Exit,但它的输出可能被缓冲、延迟,不如手动控制可靠
HTTP 服务崩溃需单独包裹每个 handler
http.Serve 内部对每个请求启动独立 goroutine,主 goroutine 的 recover 对 request handler 的 panic 完全无效。必须为每个 handler 加一层保护。
诊断并恢复通过 SSH 隧道连接的 OpenClaw 节点。用于解决配对必需错误、隧道冲突、远程端点错误以及 SSH 目标配置错误等问题。
推荐封装一个中间件:
func recoverHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("[HTTP PANIC] %s %s: %v", r.Method, r.URL.Path, r)
log.Printf("[HTTP STACK] %s", debug.Stack())
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
// 使用:
http.Handle("/api/", recoverHandler(http.HandlerFunc(myHandler)))
容易踩的坑:
- 别只 wrap
http.HandleFunc,要 wrap 整个http.Handler链(比如用了 Gorilla Mux、Chi 等路由库,得在 router.ServeHTTP 前 defer) - 不要在 handler 里直接
panic后期望上层统一 recover —— Go HTTP server 默认会打印 panic 到 stderr,但不带堆栈、不格式化、不可控 - 若 handler 返回了
http.Error,不代表安全;仍可能在 write header/body 过程中 panic(如 JSON 编码空指针),所以 wrapper 必须包住next.ServeHTTP全过程
goroutine 泄漏风险:recover 后别忘清理资源
recover 只是停止 panic 传播,它不会回滚变量状态、关闭文件、释放锁或取消 context。如果 panic 发生在持有资源的中间,recover 后继续执行可能造成泄漏或死锁。
典型场景:
- 打开文件后 panic → recover 后没
Close()→ 文件句柄泄露 - 加了
mu.Lock()后 panic → recover 后没mu.Unlock()→ 其他 goroutine 卡死 - 启了子 goroutine 并传了
context.WithCancel→ panic 后没cancel()→ context 泄漏
解决思路不是避免 recover,而是把资源管理交给 defer 本身(在 panic 前注册):
func riskyOp() {
f, err := os.Open("data.txt")
if err != nil { panic(err) }
defer f.Close() // ✅ 即使 panic 也会执行
mu.Lock()
defer mu.Unlock() // ✅
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() // ✅
// ... 可能 panic 的操作
}
真正难处理的是跨函数/跨 goroutine 的资源绑定 —— 这时候 recover 就只是兜底日志,不能替代严谨的错误传播和 cleanup 设计。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










