Go 中 time.Ticker 定时任务需在每次 tick 内用 defer+recover 独立捕获 panic,避免 goroutine 崩溃中断;不可将 recover 放在外层;应区分 panic(运行时错误)与 error(业务错误)处理,并结合 context 实现优雅退出。
在 Go 中用 time.Ticker 实现定时任务时,若任务函数内部 panic,整个 goroutine 会崩溃,Ticker 不会自动恢复,导致定时逻辑中断——这是常见但容易被忽视的风险。正确做法是在每个 tick 的执行中
独立 recover,隔离错误,保障定时器持续运行。
不能把 recover 放在启动 goroutine 的外层,必须放在每次 t.C 触发后的处理函数内部,否则一次 panic 就终止整个循环。
示例写法:
ticker := time.NewTicker(5 * time.Second) defer ticker.Stop()go func() { for range ticker.C { // ✅ 每次 tick 都新建独立的 recover 上下文 func() { defer func() { if r := recover(); r != nil { log.Printf("task panicked: %v", r) // 可选:上报、告警、记录指标 } }() doWork() // 可能 panic 的业务逻辑 }() } }()
recover 不是万能兜底,它只应捕获**预期外的运行时 panic**(如空指针、切片越界),而不该用于处理业务错误(比如 API 调用失败)。后者应返回 error 并由上层判断重试或告警。
建议区分处理:
单纯用 time.Ticker + 无限 for-range,在程序退出时可能无法及时停止。应结合 context.Context 主动退出循环。
改进结构:
ctx, cancel := context.WithCancel(context.Background()) defer cancel()go func() { defer func() { if r := recover(); r != nil { log.Printf("ticker goroutine recovered: %v", r) } }() for { select { case <-ticker.C: func() { defer func() { if r := recover(); r != nil { log.Printf("task failed: %v", r) } }() doWork() }() case <-ctx.Done(): return // 正常退出 } } }()
如果定时任务重要性高、需持久化、支持暂停/动态调整,原生 time.Ticker 易出错且难维护。可考虑:
time.AfterFunc + 递归调度 + context 控制,比 Ticker 更易测试和中断