定时任务中未recover的panic不会直接退出程序,但若其goroutine是唯一活跃协程(如主函数已返回),进程将终止;正确做法是在每次tick或AfterFunc回调内部用defer recover()捕获。

定时任务里 panic 会导致整个程序退出吗
会。Go 的 time.Ticker 或 time.AfterFunc 启动的 goroutine 如果发生 panic,且未被 recover,会终止该 goroutine,但不会直接 kill 主程序——不过如果这是唯一活跃的 goroutine(比如主函数已 return),进程就结束了。更常见的是:定时任务反复 panic → 日志刷屏 → 业务逻辑静默失效,比 crash 更难排查。
在 timer.Tick 循环里加 recover 的正确位置
recover 必须在 panic 发生的同一 goroutine 中、且在 defer 里调用才有效。不能写在主 goroutine,也不能漏掉 defer。常见错误是把 recover 放在定时器创建前,或只包了一层函数没包实际执行体。
- ✅ 正确:每个 tick 触发的匿名函数内部用
defer recover() - ❌ 错误:只在启动 timer 的地方 defer,不覆盖每次执行
- ❌ 错误:recover 写在独立函数里但没用 defer 包裹
示例:
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
<p>go func() {
for range ticker.C {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in ticker job: %v", r)
}
}()
doSomething() // 这里 panic 才会被捕获
}
}()用 time.AfterFunc 时 recover 容易漏掉什么
time.AfterFunc 是一次性任务,但常被循环调用模拟周期行为。这时每轮都得单独加 recover,否则第二轮 panic 就逃逸了。
立即学习“go语言免费学习笔记(深入)”;
- 必须在每次
AfterFunc的回调函数内写defer recover() - 不能依赖外层函数的 defer——它只运行一次
- 如果用递归式重调度(比如回调末尾再调
AfterFunc),recover 要包住整个回调体,包括重调度语句
示例(安全的递归调度):
func runPeriodic() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in periodic task: %v", r)
}
}()
doWork()
time.AfterFunc(10*time.Second, runPeriodic) // recover 已覆盖本次调用全生命周期
}recover 后要不要手动重启 goroutine 或 timer
不需要自动重启。recover 只是防止崩溃,goroutine 本身在 recover 后就结束了。对 time.Ticker,for-range 循环天然持续;对 AfterFunc,需确保重调度逻辑不因 panic 被跳过——所以重调度语句必须放在 defer/recover 块**之后**,或像上例那样包进 recover 范围内。
真正容易被忽略的是日志上下文:recover 拿不到 panic 的 stack trace,除非显式调用 debug.PrintStack() 或用 runtime/debug.Stack() 捕获。线上建议至少记录 stack,否则只能看到 “panic: xxx”,没法定位哪行代码崩的。


















