recover只能捕获当前goroutine的panic,子协程panic必须在内部用defer+recover处理,否则进程可能崩溃;需避免recover后继续执行、二次panic,并通过channel等显式传递错误。

子协程 panic 无法被主 goroutine 的 recover 捕获
Go 的 recover 只对**当前 goroutine** 有效,主 goroutine 里写的 defer func() { recover() }() 对子协程里的 panic 完全无效。这是最常踩的坑——加了 recover 却照样崩溃,控制台打出完整 stack trace 后进程退出。
典型错误现象:panic: runtime error: index out of range 直接终止程序,哪怕外面包了 waitgroup.Wait() 也拦不住。因为子协程是独立调度单元,它自己没设 recover,就直接终止并上报给 runtime,父 goroutine 不知情、不中断、也不接管。
- 子协程 panic 后不会传播到主 goroutine,但整个进程仍可能退出(尤其当主线程很快结束时)
- 即使主流程还在跑,后台 worker 可能已静默退出,导致资源泄漏、任务中断、监控失察
-
recover()必须写在子协程内部,且必须包裹在defer的匿名函数中(不能写成defer recover(),那会在注册时就执行)
每个子协程必须独立注册 defer + recover
不是“统一兜底”,而是“各自负责”。只要子协程里有不可信操作(比如解析用户输入、访问未判空的 map、类型断言、第三方库调用),就得自己包一层 defer/recover。
标准写法示例:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("goroutine panicked: %v", r)
// 建议加上 debug.Stack() 获取完整堆栈
}
}()
m := make(map[string]int
_ = m["missing"] // 触发 panic,但会被捕获
}()
-
recover必须早于可能 panic 的代码执行,靠defer保证注册时机 - 不要只打印一句日志就完事:生产环境应注入 trace ID、记录堆栈(用
debug.Stack(),不是debug.PrintStack())、触发告警或上报监控 - recover 后不能继续执行原业务逻辑——状态很可能已不一致;应立即
return或转入明确错误分支(如关闭资源、重试、降级)
用 safeGo 封装避免重复写 defer/recover
重复手写容易漏、易出错,封装一个通用函数是合理选择,但别牺牲可观察性。
轻量封装示例(带日志和堆栈):
func safeGo(f func(), logger *log.Logger) {
go func() {
defer func() {
if r := recover(); r != nil {
stack := debug.Stack()
if logger != nil {
logger.Printf("panic in goroutine: %+v\n%s", r, stack)
}
}
}()
f()
}()
}
- 调用时传入具体业务函数和 logger:
safeGo(func() { doRiskyWork() }, myLogger) - 避免在
recover块里再触发 panic(比如向已关闭 channel 发送、对 nil map 写入),否则原始 panic 被覆盖,彻底丢失线索 - HTTP handler、定时任务、长连接守护协程是高频漏点,这些地方务必用
safeGo或等效机制兜住
recover 后的状态不可信,别试图“续上”原逻辑
recover 只是让 goroutine 不崩溃,但它**不会回滚栈、不会重置状态、不会自动续上 panic 前的语句**。常见误用是 recover 后接着往下跑,结果数据错乱或逻辑跳变。
- panic 发生后,函数已退出,局部变量、锁状态、channel 状态都可能处于中间态
- 不要在 recover 块里调用可能再次 panic 的函数(比如又去读一个未初始化的指针)
- 如果需要向主 goroutine 传递错误,用
chan error或sync.Once + atomic.Value显式通知,而不是靠“恢复后继续执行”来隐式同步 - Context 取消和 panic 处理要分开考虑:
ctx.Done()不会拦截 panic,panic 也不会主动关闭 context
实际中最容易被忽略的是:recover 后不做显式退出,或者把 recover 当成 error 处理来用。它本质是异常逃生舱,不是常规错误流。一旦 panic 发生,那个 goroutine 的业务上下文已经失效,硬撑着往下走,比直接退出更危险。











