17370845950

如何在 Go 中使用 cron 定时执行函数(含阻塞与信号处理详解)

本文详解 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 即可安全退出。这正是构建可靠定时任务服务的基础范式。