
goroutine 里 panic 不会传播到主线程
Go 的 goroutine 是独立的执行单元,内部 panic 不会自动冒泡到启动它的函数或 main 函数。这意味着:你写了个 go func() { panic("boom") }(),程序不会崩溃,也不会报错——它就静默死了,还可能泄漏资源。
常见错误现象:goroutine 突然不干活了、日志断了、HTTP handler 响应变慢甚至超时,但主程序照常运行,recover 完全没被触发——因为你根本没在那个 goroutine 里放 recover。
- 必须在每个可能 panic 的
goroutine内部用defer+recover捕获 - 不要指望外层函数能“兜住”子
goroutine的 panic - 如果用
sync.WaitGroup等待,panic 后Done()可能漏调,导致死等
正确写法:defer recover 必须在 goroutine 内部
不是“外面包一层 recover”,而是每个并发逻辑自己负责自己的异常兜底。典型结构就是 go func() { defer func() { if r := recover(); r != nil { /* 记录 */ } }(); /* 业务代码 */ }()。
使用场景:HTTP handler 启动后台任务、定时器回调、消息队列消费者、数据库连接池健康检查等所有非主线程执行路径。
立即学习“go语言免费学习笔记(深入)”;
-
recover只在defer函数中有效,且只对同 goroutine 的 panic 生效 - 别把
recover放在外部函数里——它对别的 goroutine 无感 - 恢复后建议记录
r(可能是string、error或其他类型),别直接丢弃 - 示例:
go func() { defer func() { if r := recover(); r != nil { log.Printf("panic in worker: %v", r) } }() doWork() // 这里可能 panic }()
recover 后如何安全退出 goroutine
recover 只是止住 panic 的传播,goroutine 还在运行。如果不清掉上下文、不释放锁、不关闭 channel,可能引发数据竞争或资源泄漏。
性能 / 兼容性影响:反复 panic + recover 是昂贵操作,不该作为控制流;但偶尔兜底比进程崩掉强得多。
- recover 后别继续执行敏感逻辑(比如再写一次数据库)
- 如果用了
context.Context,检查是否已Done(),及时 return - 确保所有
defer注册的清理逻辑(如file.Close()、mu.Unlock())仍会执行 - 向结果 channel 发送零值 or 错误前,先判断 channel 是否已 close,避免 panic
用 errgroup 或 worker pool 封装 recover 更可靠
手写每个 go defer recover 容易漏、难维护。用 errgroup.Group 或封装好的 worker 池,能把 recover 统一收口。
参数差异:errgroup.WithContext 自带 cancel 控制,适合有生命周期管理的场景;纯 errgroup.Group 则更轻量。
-
errgroup.Group的Go方法内部不自动 recover,你仍需在传入函数里自己加 - 推荐封装一个
SafeGo工具函数:func SafeGo(f func()) { go func() { defer func() { if r := recover(); r != nil { log.Printf("safe go panic: %v", r) } }() f() }() } - 第三方库如
github.com/oklog/run也默认不 recover,本质一样——异常兜底永远得在 goroutine 内发生
事情说清了就结束:recover 不是开关,是每个 goroutine 自己的逃生舱门;漏掉一个,就少一道防线。










