cron.New() 创建未激活调度器,需 go c.Start() 启动并阻塞主 goroutine;须传 cron.WithLocation(time.Local) 适配本地时区;应加 cron.WithChain(cron.Recover(...)) 防 panic 停摆;秒级任务推荐 time.Ticker 而非 cron 表达式。

cron.New() 默认不启动调度器,也不按本地时区运行,更不自动捕获 panic —— 直接用会静默失败、时间错乱、一崩全停。
为什么 cron.New() 启动后任务不执行
调用 cron.New() 只是创建一个未激活的调度器实例;必须显式调用 .Start() 才开始 tick。但常见错误是:c.Start() 写在 main() 末尾,导致函数立即返回、进程退出。
- ✅ 正确做法:用
go c.Start()启动 goroutine,再用signal.Notify阻塞主 goroutine 等待中断信号 - ❌ 错误写法:
c.Start(); fmt.Println("done")——fmt打印完程序就退出了,任务根本没机会触发 - ⚠️ 注意:v3 版本默认不启用秒级支持,若表达式写成
"* * * * * *"(6 段)却没传cron.WithSeconds(),解析会静默失败,任务永不注册
怎么让 "0 0 * * *" 真正在北京时间 0 点跑
默认所有 cron 实例使用 time.UTC 解析表达式,哪怕你机器 TZ=Asia/Shanghai 也无效。结果就是你写的“每天 0 点”,实际在 UTC 0 点(即北京时间上午 8 点)触发。
- ✅ 必须初始化时传入:
cron.New(cron.WithLocation(time.Local)) - ❌ 不要硬编码
time.LoadLocation("Asia/Shanghai")—— 容器环境常缺失时区数据,time.Local更可靠 - ? 验证方式:打印
c.Entry().Schedule.Next(time.Now()),看返回时间的小时是否符合预期(比如当前是北京时间 23:59,下一触发应为次日 0:00)
AddFunc 里 panic 会导致整个调度器停摆
cron/v3 默认串行执行所有任务,且不 recover 任何 panic。一个任务空指针或除零,整个 c.Run() 流程退出,后续所有任务永久失效,且无日志提示。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 推荐方案:初始化时加
cron.WithChain(cron.Recover(cron.DefaultLogger)),它会在每个任务外层自动包一层defer recover - ⚠️ 注意:它只捕获 panic,不处理函数返回的
error;业务错误仍需主动判断和记录 - ? 避免闭包捕获循环变量:
for i := range jobs { c.AddFunc("...", func() { use(i) }) }—— 所有任务最终都用最后一个i值;应改用显式参数传入:func(i int) { ... }(i)
秒级任务别硬套 cron 表达式
cron/v3 的设计目标是类 Unix cron,最小原生粒度是分钟。即使开了 cron.WithSeconds(),它仍是靠每秒 tick + 遍历所有 job 判断是否该触发,不适合高密度、低延迟场景。
- ✅ 每秒/每 5 秒这种需求,直接用
time.Ticker+time.AfterFunc更轻量、可控、无漂移 - ❌ 不要用
"*/5 * * * * *"+ 任务内time.Sleep(5 * time.Second)—— 会因执行耗时导致堆积、延迟放大、不可预测 - ? 超过 50 个任务或要求误差 cron 的线性扫描机制就开始不准;此时应考虑
gocron(基于最小堆)或自研时间轮
真正麻烦的不是写对一行表达式,而是理解它背后没有魔法——所有“准时”都依赖你控制好时区、panic、goroutine 生命周期和精度边界。这些点漏掉任何一个,线上就只能靠日志盲猜。


















