最常见原因是未调用c.Start()或主goroutine立即退出;必须显式调用c.Start()并阻塞main(如signal.Notify),否则任务注册成功但永不触发,进程闪退。

为什么 robfig/cron 启动后任务不执行?
最常见原因是没调用 c.Start(),或者启动后立即退出了主 goroutine。这个库不会阻塞主线程,启动后若 main 函数结束,整个进程就退出了。
必须确保:c.Start() 调用后,程序保持运行(比如用 select{} 或 signal.Notify 等待中断)。
- 漏掉
c.Start()→ 任务注册成功但完全不触发 - main 函数末尾没阻塞 → 进程闪退,日志都看不到
- 使用
c.AddFunc()时传入的函数 panic 了 → 默认会静默丢弃,需手动加 recover 或用c.WithChain(c.Recover())
如何安全地添加定时任务并控制并发?
robfig/cron 默认是串行执行的:同一时间最多一个 job 在跑。如果想允许并发(比如多个独立采集任务),得显式配置 c.WithChain(c.ParallelWithLimit(n))。
注意:ParallelWithLimit 是 per-cron 实例的全局限制,不是 per-job 的;且它只限制“正在执行”的 job 数量,排队中的不计入。
立即学习“go语言免费学习笔记(深入)”;
- 默认无并发控制 → 所有 job 按 cron 表达式触发,但串行排队执行
-
c.WithChain(c.ParallelWithLimit(3))→ 最多 3 个 job 同时运行,超限的任务会等待空闲 slot - 如果某个 job 长时间阻塞(如 HTTP 请求卡住),会占着 slot 不放,建议在 job 内部加 context 和超时
- 不要在
AddFunc的函数里直接起 goroutine 并发 —— 这绕过了 cron 的调度和限流逻辑,容易失控
robfig/cron 的 cron 表达式和标准 Linux 有何差异?
默认使用的是「6 字段」格式(秒 分 时 日 月 周),而 Linux crontab 是 5 字段(不含秒)。如果你照抄系统 crontab 表达式,会少一位、解析失败或行为错乱。
常见错误:"0 0 * * *" → 实际被解析成「第 0 秒、第 0 分、每天 0 点」,而不是「每天 0 点整」—— 因为它被当成了 6 字段,第 6 位缺省为 *(任意周几),但前 6 位中第 1 位是秒,所以其实是每分钟第 0 秒执行。
- 要写「每天 0 点」→ 用
"0 0 0 * * *"(6 字段:秒=0,分=0,时=0,日=*,月=*,周=*) - 要兼容 5 字段习惯 → 初始化 cron 时用
cron.New(cron.WithSeconds())显式启用秒字段,否则它会按 5 字段解析(即忽略第一个数字) - 推荐始终显式指定字段数,避免歧义:要么全用 6 字段 +
WithSeconds(),要么统一用 5 字段 + 不启用秒
怎么优雅停止 cron 并等待正在运行的 job 结束?
c.Stop() 会立刻停止新 job 调度,但**不会等待已开始的 job 完成**。如果 job 正在写文件、发请求或处理事务,强行退出可能导致数据不一致。
真正安全的做法是:先 c.Stop() 阻断新调度,再手动等待所有活跃 job(需自己跟踪)——但 robfig/cron 不暴露活跃 job 列表。所以更现实的方案是:让每个 job 自己支持 context 取消,并在收到信号后主动退出。
- 在
AddFunc中接收一个context.Context(需封装 wrapper) - 主程序监听
os.Interrupt,发送 cancel 信号,然后 sleep 短暂时间(如 5s)再彻底退出 - 避免依赖
c.Stop()做资源清理 —— 它只是关掉调度器,不是 graceful shutdown 机制 - 如果 job 必须强保证完成,考虑改用
github.com/robfig/cron/v3(v3 版本支持Stop()等待运行中 job,但 API 不兼容 v1/v2)
v2 和 v3 的 import path、API、默认字段数都不一样,升级前务必检查文档。很多人卡在 “为什么 Stop 不等待” 上,其实是因为用的是 v2 —— 它压根没这个能力。


















