recover无法捕获子协程panic,必须在每个子协程内单独使用defer/recover处理;recover后应立即return或进入错误分支,不可继续原逻辑执行。

子协程 panic 无法被外层 defer recover 捕获
Go 的 recover 只对**当前 goroutine** 中的 panic 有效。主 goroutine 里写的 defer func() { recover() }() 对子协程里的 panic 完全无感——这是最常踩的坑,也是很多人以为“加了 recover 就稳了”的根源。
典型错误现象:panic: runtime error: index out of range 依然直接崩溃,控制台打印 full stack trace,程序退出。
- 子协程是独立调度单元,panic 发生时它自己没设
recover,就直接终止并向上抛给 runtime - 父 goroutine 不知情,也不会中断或收到通知
- 即使父 goroutine 正在
waitgroup.Wait(),也拦不住 panic 导致的进程退出
必须在每个可能 panic 的子协程内部做 recover
不是“统一兜底”,而是“各自负责”。只要子协程里有不可信操作(比如解析用户输入、访问 map 未判空、类型断言、第三方库调用),就得自己包一层 defer/recover。
示例:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("goroutine panicked: %v", r)
}
}()
// 这里写可能 panic 的逻辑
m := make(map[string]int)
_ = m["missing"] // 触发 panic,但会被捕获
}()
- recover 必须放在子协程函数体内,且要早于可能 panic 的代码执行(靠 defer 保证)
- 不要只 recover 不记录——静默吞掉 panic 会让问题更难排查
- 如果需要向主 goroutine 传递错误,用
chan error或sync.Once + atomic.Value等方式显式通知
recover 后不能“继续执行”原逻辑
recover 只是让 goroutine 不崩溃,但它**不会回滚栈、不会重置状态、不会自动续上 panic 前的语句**。常见误用是 recover 后接着往下跑,结果数据错乱或逻辑跳变。
- recover 后应立即 return,或转入明确的错误处理分支(如重试、降级、关闭资源)
- 不要在 recover 块里调用可能再次 panic 的函数(比如又去读一个未初始化的指针)
- 注意 defer 链:recover 只影响当前 goroutine 的 panic 流程,已注册的其他 defer 仍会按序执行
Context 取消和 panic 处理要分开考虑
有人想用 ctx.Done() 拦截子协程退出,但这和 panic 无关——context cancel 不会触发 panic,panic 也不会主动关闭 context。两者是正交问题。
- panic 是异常终止,context cancel 是协作式退出,不能互相替代
- 若需在 panic 后清理资源,把 cleanup 逻辑写进 defer(在 recover 前或后都可),别依赖 context
- 如果子协程持有锁、文件句柄、网络连接,recover 后务必显式释放,否则可能泄漏
实际编码中最容易漏的是:子协程里调用了一个看似安全的函数,但它内部用了反射或 map 访问,而你没看源码——这种 panic 就算写了 recover 也可能被忽略。所以,对任何非 trivial 的子协程,加 recover 不是过度防御,是基本操作。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










