
本文详解 go 中使用 robfig/cron 库实现定时任务的正确姿势,重点解决程序启动后立即退出、cron 表达式误用及进程常驻问题,并提供可运行的完整示例与关键注意事项。
在 Go 中使用 github.com/robfig/cron(v2 及以下版本,注意:该库已归档,推荐生产环境迁移到 github.com/robfig/cron/v3 或 github.com/go-co-op/gocron)实现定时任务时,一个常见误区是:调用 c.Start() 后未保持主 goroutine 活跃,导致程序瞬间退出,定时器根本来不及触发。
例如原始代码中:
c.Start() // 启动调度器,但 main 函数随即结束
此时 main 函数执行完毕,整个进程终止,即使 cron 已启动也无济于事。
✅ 正确做法:保持主 goroutine 阻塞 + 合理配置表达式
首先,修正 Cron 表达式:原 "1 * * * * *" 表示「每分钟第 1 秒执行」(即每 60 秒一次),而非「每秒执行」。若需每秒触发,请使用六字段格式的 * * * * * *(robfig/cron v2 默认支持秒级,字段顺序为:秒 分 时 日 月 周)。
其次,必须让 main 函数持续运行。推荐方式是监听系统中断信号(如 Ctrl+C),优雅等待退出:
package main
import (
"fmt"
"os"
"os/signal"
"time"
"github.com/robfig/cron"
)
func main() {
c := cron.New()
// ✅ 每秒执行一次(六位表达式)
c.AddFunc("* * * * * *", RunEverySecond)
// ✅ 在 goroutine 中启动 cron,避免阻塞 main
go c.Start()
// ✅ 等待 OS 信号(如 SIGINT/Ctrl+C),防止程序退出
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, os.Kill)
<-sig // 阻塞在此,直到收到信号
fmt.Println("Shutting down...")
c.Stop() // 可选:优雅停止调度器
}
func RunEverySecond() {
fmt.Printf("[%s] Tick!\n", time.Now().Format("15:04:05"))
}⚠️ 关键注意事项
- 不要直接调用 c.Start() 同步阻塞:它会阻塞当前 goroutine,而 main 中阻塞会导致无法注册信号监听。
- 务必使用 go c.Start() + 外部同步机制(如 signal.Notify + channel receive)。
- 表达式字段数需匹配:robfig/cron v2 默认解析 6 字段(含秒),若误用 5 字段(如 "* * * * *"),将被解释为「每分钟执行」,且秒字段默认为 0。
-
生产建议:
- 升级至 cron/v3(需显式启用秒级:cron.WithSeconds());
- 或选用更活跃维护的替代库如 gocron,API 更现代、内置上下文支持与错误处理。
运行上述代码后,你将看到每秒输出一行时间戳,按下 Ctrl+C 即可安全退出。这正是构建可靠定时任务服务的基础范式。










