如何在 Go 中使用 cron 定时执行方法(含完整可运行示例)

本文详解如何在 go 程序中正确使用 robfig/cron 库实现定时任务,解决因主 goroutine 过早退出导致任务不执行的问题,并提供带信号监听的健壮启动模式。

在 Go 中使用 github.com/robfig/cron(v2 及以下版本)实现定时任务时,一个常见误区是:程序启动 cron 后立即退出,导致后台任务无机会执行。这是因为 cron.Start() 启动的是一个后台 goroutine,而 main() 函数若执行完毕,整个进程就会终止——无论 cron 是否已注册任务。

? 问题定位

原始代码存在两个关键问题:

  • Cron 表达式错误:"1 * * * * *" 表示“秒字段为 1 时触发”,即每分钟第 1 秒执行一次(并非每秒),实际是每 60 秒执行一次;
  • 缺少主程序阻塞机制:c.Start() 后未保持主线程存活,main() 函数结束 → 进程退出 → 所有 goroutine 被强制终止。

✅ 正确做法:启动 + 持续等待 + 优雅退出

推荐使用 os/signal 监听系统中断信号(如 Ctrl+C),让主 goroutine 阻塞等待,同时允许用户主动终止服务:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "time"

    "github.com/robfig/cron"
)

func main() {
    c := cron.New()

    // ✅ 正确的每秒执行表达式(6 字段 cron,支持秒)
    c.AddFunc("* * * * * *", RunEverySecond)

    // ✅ 在 goroutine 中启动 cron(非阻塞)
    go c.Start()

    // ✅ 主 goroutine 等待中断信号(如 Ctrl+C)
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, os.Interrupt, os.Kill)
    fmt.Println("Cron started. Press Ctrl+C to exit.")
    <-sig // 阻塞至此,直到收到信号

    fmt.Println("Shutting down...")
    c.Stop() // ✅ 建议显式调用 Stop() 释放资源(v2+ 支持)
}

func RunEverySecond() {
    fmt.Printf("[%s] Task executed\n", time.Now().Format("15:04:05"))
}

⚠️ 注意事项与最佳实践

  • Cron 表达式格式:robfig/cron v2 默认支持 6 字段(秒 分 时 日 月 周),如需每秒执行,请用 "* * * * * *";若误用 5 字段(如 "* * * * *"),库会自动补零为 "0 * * * * *"(即每分钟第 0 秒执行)。
  • 避免 c.Start() 后直接 time.Sleep():虽可临时阻塞,但不响应中断、不可控且不专业。
  • 资源清理:调用 c.Stop() 可关闭内部 ticker 并防止 goroutine 泄漏(v2+ 版本推荐)。
  • 生产环境建议:考虑使用更现代的替代库(如 github.com/go-co-op/gocron)或 time.Ticker + select 实现轻量级轮询;若需分布式/持久化调度,应选用专有服务(如 Quartz、Temporal)。

通过以上结构,你的 Go 定时任务将稳定运行、可观测、可中断,真正满足长期守护型作业需求。