Golang学习笔记:定时crontab
Cron库
Golang 的 cron 库用于处理定时任务,其中 github.com/robfig/cron/v3 是一个广泛使用的、功能丰富的库,它支持标准的 cron 表达式,并且易于使用
安装
go get github.com/robfig/cron/v3
基本用法
1. 最简单的定时任务
package mainimport ("fmt""log""time""github.com/robfig/cron/v3"
)func main() {c := cron.New()// 添加任务_, err := c.AddFunc("* * * * *", func() {fmt.Printf("每分钟执行: %s\n", time.Now().Format("15:04:05"))})if err != nil {log.Fatal(err)}c.Start()fmt.Println("定时任务已启动")// 保持程序运行select {}
}
2. 秒级精度任务
package mainimport ("fmt""time""github.com/robfig/cron/v3"
)func main() {// 使用秒级精度c := cron.New(cron.WithSeconds())// 每30秒执行一次_, _ = c.AddFunc("*/30 * * * * *", func() {fmt.Printf("每30秒执行: %s\n", time.Now().Format("15:04:05"))})c.Start()select {}
}
Cron 表达式
标准表达式(5字段)
c := cron.New() // 默认分钟级精度// 格式: 分 时 日 月 周
_, _ = c.AddFunc("0 * * * *", func() { // 每小时fmt.Println("每小时执行")
})_, _ = c.AddFunc("30 2 * * *", func() { // 每天2:30fmt.Println("每天2:30执行")
})_, _ = c.AddFunc("0 9 * * 1", func() { // 每周一9:00fmt.Println("每周一9:00执行")
})
秒级表达式(6字段)
c := cron.New(cron.WithSeconds())// 格式: 秒 分 时 日 月 周
_, _ = c.AddFunc("0 0 * * * *", func() { // 每小时fmt.Println("每小时执行")
})_, _ = c.AddFunc("0 */5 * * * *", func() { // 每5分钟fmt.Println("每5分钟执行")
})
预定义表达式
c := cron.New()_, _ = c.AddFunc("@yearly", func() { // 每年一次fmt.Println("每年执行")
})_, _ = c.AddFunc("@monthly", func() { // 每月一次fmt.Println("每月执行")
})_, _ = c.AddFunc("@weekly", func() { // 每周一次fmt.Println("每周执行")
})_, _ = c.AddFunc("@daily", func() { // 每天一次fmt.Println("每天执行")
})_, _ = c.AddFunc("@hourly", func() { // 每小时一次fmt.Println("每小时执行")
})_, _ = c.AddFunc("@every 1h30m", func() { // 每1小时30分钟fmt.Println("每1.5小时执行")
})
高级功能
1. 错误恢复和链式操作
package mainimport ("fmt""log""time""github.com/robfig/cron/v3"
)func main() {c := cron.New(cron.WithChain(cron.Recover(cron.DefaultLogger), // 恢复paniccron.DelayIfStillRunning(cron.DefaultLogger), // 延迟执行如果任务还在运行),cron.WithSeconds(),)_, err := c.AddFunc("*/10 * * * * *", func() {fmt.Printf("任务开始: %s\n", time.Now().Format("15:04:05"))// 模拟长时间运行的任务time.Sleep(15 * time.Second)fmt.Printf("任务结束: %s\n", time.Now().Format("15:04:05"))})if err != nil {log.Fatal(err)}c.Start()time.Sleep(1 * time.Minute)c.Stop()
}
2. 自定义日志和时区
package mainimport ("log""os""time""github.com/robfig/cron/v3"
)func main() {// 创建特定时区(例如上海时区)shanghaiLoc, err := time.LoadLocation("Asia/Shanghai")// 自定义日志logger := cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))c := cron.New(cron.WithLogger(logger),cron.WithSeconds(),cron.WithLocation(shanghaiLoc), // 使用上海时区)_, _ = c.AddFunc("*/5 * * * * *", func() {log.Println("任务执行")})c.Start()time.Sleep(30 * time.Second)c.Stop()
}
3. 使用 Job 接口
package mainimport ("fmt""time""github.com/robfig/cron/v3"
)// 自定义Job类型
type EmailJob struct {To string
}func (j EmailJob) Run() {fmt.Printf("发送邮件给 %s: %s\n", j.To, time.Now().Format("15:04:05"))
}type CleanupJob struct{}func (j CleanupJob) Run() {fmt.Printf("执行清理任务: %s\n", time.Now().Format("15:04:05"))
}func main() {c := cron.New()// 使用AddJob方法_, _ = c.AddJob("@every 1m", EmailJob{To: "user@example.com"})_, _ = c.AddJob("0 */5 * * * *", CleanupJob{})c.Start()time.Sleep(10 * time.Minute)c.Stop()
}
实际项目应用
1. 任务管理封装
package taskmanagerimport ("fmt""sync""time""github.com/robfig/cron/v3"
)type TaskManager struct {cron *cron.Crontasks map[string]cron.EntryIDmu sync.RWMutex
}func NewTaskManager() *TaskManager {return &TaskManager{cron: cron.New(cron.WithSeconds()),tasks: make(map[string]cron.EntryID),}
}// 添加任务
func (tm *TaskManager) AddTask(name, schedule string, task func()) error {tm.mu.Lock()defer tm.mu.Unlock()entryID, err := tm.cron.AddFunc(schedule, task)if err != nil {return err}tm.tasks[name] = entryIDfmt.Printf("任务 '%s' 已添加\n", name)return nil
}// 移除任务
func (tm *TaskManager) RemoveTask(name string) error {tm.mu.Lock()defer tm.mu.Unlock()entryID, exists := tm.tasks[name]if !exists {return fmt.Errorf("任务 '%s' 不存在", name)}tm.cron.Remove(entryID)delete(tm.tasks, name)fmt.Printf("任务 '%s' 已移除\n", name)return nil
}// 获取任务列表
func (tm *TaskManager) ListTasks() []string {tm.mu.RLock()defer tm.mu.RUnlock()var tasks []stringfor name := range tm.tasks {tasks = append(tasks, name)}return tasks
}// 启动调度器
func (tm *TaskManager) Start() {tm.cron.Start()fmt.Println("任务管理器已启动")
}// 停止调度器
func (tm *TaskManager) Stop() {tm.cron.Stop()fmt.Println("任务管理器已停止")
}// 使用示例
func main() {tm := NewTaskManager()// 添加多个任务_ = tm.AddTask("quick_scan", "*/10 * * * * *", func() {fmt.Printf("快速扫描: %s\n", time.Now().Format("15:04:05"))})_ = tm.AddTask("daily_report", "0 0 9 * * *", func() {fmt.Printf("生成日报: %s\n", time.Now().Format("15:04:05"))})tm.Start()// 运行一段时间后移除任务time.Sleep(30 * time.Second)_ = tm.RemoveTask("quick_scan")time.Sleep(10 * time.Second)tm.Stop()
}
注意事项
需要注意的是,robfig/cron 库有 v1 和 v3 等主要版本,它们在 API 和默认行为上有所不同(例如 v3 默认不支持秒级精度,需通过 cron.WithSeconds() 开启)。建议使用 v3 版本,
time定时
运行原理
time.Ticker 基于 Go 运行时的四叉堆定时器管理机制,通过单个调度 goroutine 统一处理所有周期性任务,使用绝对时间和通道缓冲实现高精度、高性能的周期触发,无需为每个定时器创建单独 goroutine。参考
一次性定时使用 time.Timer,触发一次后就会从运行时定时器堆中移除,不会重新调度,而 周期性定时 的 time.Ticker 会在每次触发后重新计算下次时间并重新插入堆中实现循环触发。
基本用法
1. 最简单用法
package mainimport ("log""time"
)func main() {// 每5秒执行一次ticker := time.NewTicker(5 * time.Second)defer ticker.Stop() // 确保退出时停止tickerlog.Println("开始周期性任务...")for {select {case <-ticker.C:log.Printf("周期性任务执行: %s", time.Now().Format("15:04:05"))}}
}
2. 带退出机制的 Ticker
package mainimport ("log""os""os/signal""syscall""time"
)func main() {// 创建带退出信号的tickerticker := time.NewTicker(5 * time.Second)done := make(chan bool)quit := make(chan os.Signal, 1)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)go func() {for {select {case <-done:returncase t := <-ticker.C:log.Printf("任务执行: %s", t.Format("15:04:05"))}}}()log.Println("周期性任务已启动,按 Ctrl+C 停止...")// 等待退出信号<-quitlog.Println("收到停止信号...")// 优雅停止ticker.Stop()done <- truelog.Println("周期性任务已停止")
}
3. 带上下文的周期性任务
package mainimport ("context""log""time"
)func periodicTask(ctx context.Context, interval time.Duration) {ticker := time.NewTicker(interval)defer ticker.Stop()for {select {case <-ctx.Done():log.Println("任务被取消")returncase <-ticker.C:log.Printf("周期性任务执行: %s", time.Now().Format("15:04:05"))}}
}func main() {// 创建可取消的上下文ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()log.Println("启动带上下文的周期性任务...")periodicTask(ctx, 5*time.Second)log.Println("程序退出")
}
