cron.New() 创建空壳需调 c.Start() 启动调度器,但必须阻塞 main(如 signal.Notify),否则进程退出;默认 UTC 时区,需显式传 cron.WithLocation(time.Local);秒级任务须用 cron.WithSeconds() 并写 6 段表达式;AddFunc 中 panic 会停摆调度器,应加 cron.Recover 中间件。

cron.New() 创建的是个空壳,不启动、不时区适配、不 recover panic —— 直接跑任务等于白配。
为什么 c.Start() 调了但任务从不执行
根本原因是:c.Start() 只是启动调度器 goroutine,它不阻塞 main;main 函数一结束,整个进程就退出,所有后台 goroutine 被强制终止。
- ❌ 错误写法:
c.Start()放在main()最后一行,后面没任何阻塞逻辑 - ✅ 正确做法:用
signal.Notify监听SIGINT或SIGTERM,然后<-sigChan阻塞住 main - ⚠️ 注意:
c.Start()在 v3 中已是异步启动,不要 再套go c.Start(),否则会重复启 goroutine,可能引发竞态或 panic
"0 0 * * *" 为什么在北京时间 8 点才跑
因为 cron.New() 默认用 time.UTC 解析表达式,哪怕你本地 TZ=Asia/Shanghai 也无效。结果就是你写的“每天 0 点”,实际按 UTC 时间触发(即北京时间上午 8 点)。
- ✅ 必须显式传
cron.WithLocation(time.Local),而不是time.LoadLocation("Asia/Shanghai")—— 容器里常缺 tzdata,time.Local更健壮 - ? 验证方式:调用
c.Entry().Schedule.Next(time.Now()),看返回时间的小时字段是否符合预期(比如当前 23:59,下一触发应为次日 00:00) - ⚠️ 注意:
time.Local依赖系统/etc/localtime或TZ环境变量,Docker 镜像需确保已挂载或设置
秒级任务必须配 cron.WithSeconds()
v3 默认只认 5 段表达式(分、时、日、月、周),写成 "* * * * * *" 会静默失败——解析返回 nil,AddFunc 不报错也不注册任务。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 启用秒级:初始化时加
cron.WithSeconds(),且表达式必须是 6 段(秒、分、时、日、月、周) - ❌ 不要混用:传了
WithSeconds()却写"0 * * * *"(5 段),任务照样不注册 - ⚠️ 表达式空格敏感:
"* * * * * *"合法,"* * * * * *"(双空格)会解析失败
AddFunc 里 panic 会导致整个调度器停摆
v3 默认串行执行所有 job,且无 panic 捕获机制。一个任务空指针或除零,c.Run() 流程直接退出,后续所有任务永久失效,还不打日志。
- ✅ 加链式中间件:
cron.WithChain(cron.Recover(cron.DefaultLogger)),它会在每个 job 外层自动加defer recover - ⚠️ 注意:它只捕获 panic,不处理函数返回的
error;业务错误仍需主动判断和记录 - ? 避免闭包捕获循环变量:
for i := range jobs { c.AddFunc("", func() { use(i) }) }→ 所有任务都用最后一个i值;应改用func(i int) { use(i) }(i)
真正容易被忽略的不是语法,而是调度器生命周期和 panic 的连锁效应:c.Start() 后没阻塞 main,程序秒退;一个未 recover 的 panic,会让所有定时任务静默死亡。这两点不处理,配置再准也没用。


















