recover必须在任务goroutine内部注册才有效,因它仅捕获当前goroutine的panic;main中defer recover对子goroutine无效,需在每个go func或ticker循环内单独defer+recover,并安全处理返回值、重试逻辑与并发状态。

recover必须在任务goroutine内部注册才有效
调度任务(比如用time.Ticker或go func()启动的周期性逻辑)通常运行在独立goroutine中。如果只在main函数里defer recover(),对这些子goroutine里的panic完全无效——recover只能捕获**当前goroutine**正在传播的panic。
常见错误是把recover写在main入口处,以为能兜住所有后台任务:
func main() {
defer func() { recover() }() // 这个recover对ticker里的panic毫无作用
go func() {
time.Sleep(time.Second)
panic("in goroutine") // 仍会崩溃整个程序
}()
select {}
}
正确做法是:每个可能panic的任务goroutine,自己负责注册defer + recover:
- 在
go func()内部第一行就写defer func(){ recover() }() - 若用
time.Ticker循环执行,recover必须放在每次循环体开头的defer里,不能只在for外注册一次 - 避免在循环内反复注册
defer func(){ recover() }()——它不会多捕获几次,只会浪费栈空间
recover后无法继续原任务逻辑,需显式重试或降级
recover只是让goroutine退出panic状态,并不恢复执行流。一旦panic发生,recover()之后的代码照常执行,但panic点之后的语句永远不运行。
例如下面这段调度任务代码:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("task panicked: %v", r)
}
}()
for range ticker.C {
doWork() // 如果这里panic,recover后会直接进入下一轮循环
cleanup() // 这行永远不会执行
}
}()
关键点:
-
cleanup()这种收尾操作必须放在doWork()之后、且不在panic路径上;否则panic时它被跳过 - 想实现“失败后重试”,不能靠recover自动续跑,得手动控制:比如在recover分支里调用
doWork()或触发下一次调度 - 若任务有副作用(如发消息、写DB),recover无法回滚,必须在
doWork()内部做幂等或补偿
recover返回值类型不确定,别硬转string
panic可以传任意类型:panic("msg")、panic(errors.New("x"))、甚至panic(42)。直接r.(string)断言会引发二次panic。
安全做法是用fmt.Sprint(r)或类型检查:
if r := recover(); r != nil {
switch v := r.(type) {
case string:
log.Printf("panic: %s", v)
case error:
log.Printf("panic: %v", v)
default:
log.Printf("panic: %v (type %T)", v, v)
}
}
注意:recover()返回的是interface{},不是error,别和常规错误处理混淆。
HTTP中间件里的recover不等于调度任务的recover
Web服务中间件(如Gin/echo的Recovery())本质也是在每个请求goroutine里做defer+recover,但它和调度任务的关键差异在于生命周期:
- HTTP handler是短生命周期,recover后返回500即可;调度任务是长运行,recover后必须决定是终止、重试还是跳过本次
- 中间件recover通常只记录日志+返回响应;调度任务recover后往往要更新状态(如标记任务失败、通知告警、调整下次执行时间)
- 别把HTTP中间件的recover逻辑直接抄到定时任务里——缺少状态管理和重入控制,容易造成重复执行或静默丢任务
最易被忽略的一点:recover本身不解决并发安全问题。如果多个调度任务共享变量(如计数器、map),panic发生时可能留下不一致状态,而recover不会帮你修复——得靠锁、原子操作或事务性设计兜底。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











