recover 只在 defer 函数中对当前 goroutine 有效,普通函数调用或跨协程均无效;其返回 nil 的根本原因是未处于 panic 栈回溯过程中的 defer 上下文。

recover 只在 defer 函数里有效,且只对当前 goroutine 生效——想靠它跨协程捕获 panic 或在普通函数里“兜底”,完全无效。
为什么 recover 总是返回 nil?
这是最常踩的坑:把 recover() 放在普通函数里调用,或者没用 defer 包裹。
根本原因:recover 的作用机制依赖于 panic 触发后的栈回溯过程。只有当 panic 正在发生、且执行流正经过一个被 defer 延迟的函数时,recover 才能截获它。
- ❌ 错误写法:
func handle() { r := recover(); fmt.Println(r) }→ 永远输出nil - ✅ 正确写法:
defer func() { if r := recover(); r != nil { /* 处理 */ } }() - ⚠️ 注意:
defer必须在 panic 发生前注册,否则来不及生效
recover 能不能捕获其他 goroutine 的 panic?
不能。每个 goroutine 的 panic 是独立的,recover 只作用于当前 goroutine 的调用栈。
立即学习“go语言免费学习笔记(深入)”;
常见误解是给 go func() { panic("boom") }() 外层加 defer/recover,这毫无意义——主 goroutine 的 recover 对子 goroutine 的 panic 完全无感。
- 子 goroutine 崩溃会直接退出,不传播,也不影响主 goroutine(除非用
sync.WaitGroup等待它) - 若需监控子 goroutine 错误,应通过 channel 或 error 返回值显式传递,而非依赖
recover - HTTP 服务中单请求 panic 能被 recover,是因为 handler 在同一 goroutine 内执行
什么时候该用 panic,而不是 error?
Go 社区共识很明确:95% 的错误场景应该用 error,panic 仅用于真正不可恢复、本不该发生的状况。
- ✅ 合理用法:
panic("config file not found")(启动时关键配置缺失) - ✅ 合理用法:
panic("nil pointer dereference in init")(明显编程错误) - ❌ 滥用:
panic处理文件不存在、网络超时、用户输入非法等可预期错误 - ⚠️ 风险:一旦
panic被recover捕获,原始错误上下文(如堆栈)就丢失了,日志里只剩一个 interface{} 值
Web 中间件里 recover 的典型陷阱
看似“兜住所有 panic”的中间件,实际容易漏掉两类情况:
- HTTP handler 里用了
go启动新 goroutine,其中 panic 不会被中间件捕获 - panic 发生在中间件自身 defer 之后的代码里(比如
next.ServeHTTP返回后又 panic),这时 recover 已执行完毕 - recover 后未重置 response writer 状态,导致后续 writeHeader/write 调用 panic(如已写 header 后再写 body)
真正健壮的做法是:在 handler 最外层 defer + recover,并确保所有异步逻辑也自带错误处理,而不是指望一层中间件包打天下。


















