Cron库
Golang 的 cron 库用于处理定时任务,其中 github.com/robfig/cron/v3 是一个广泛使用的、功能丰富的库,它支持标准的 cron 表达式,并且易于使用
安装
bash
go get github.com/robfig/cron/v3
基本用法
1. 最简单的定时任务
go
package main
import (
"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. 秒级精度任务
go
package main
import (
"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字段)
go
c := cron.New() // 默认分钟级精度
// 格式: 分 时 日 月 周
_, _ = c.AddFunc("0 * * * *", func() { // 每小时
fmt.Println("每小时执行")
})
_, _ = c.AddFunc("30 2 * * *", func() { // 每天2:30
fmt.Println("每天2:30执行")
})
_, _ = c.AddFunc("0 9 * * 1", func() { // 每周一9:00
fmt.Println("每周一9:00执行")
})
秒级表达式(6字段)
go
c := cron.New(cron.WithSeconds())
// 格式: 秒 分 时 日 月 周
_, _ = c.AddFunc("0 0 * * * *", func() { // 每小时
fmt.Println("每小时执行")
})
_, _ = c.AddFunc("0 */5 * * * *", func() { // 每5分钟
fmt.Println("每5分钟执行")
})
预定义表达式
go
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. 错误恢复和链式操作
go
package main
import (
"fmt"
"log"
"time"
"github.com/robfig/cron/v3"
)
func main() {
c := cron.New(
cron.WithChain(
cron.Recover(cron.DefaultLogger), // 恢复panic
cron.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. 自定义日志和时区
go
package main
import (
"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 接口
go
package main
import (
"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. 任务管理封装
go
package taskmanager
import (
"fmt"
"sync"
"time"
"github.com/robfig/cron/v3"
)
type TaskManager struct {
cron *cron.Cron
tasks map[string]cron.EntryID
mu 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] = entryID
fmt.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 []string
for 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. 最简单用法
go
package main
import (
"log"
"time"
)
func main() {
// 每5秒执行一次
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop() // 确保退出时停止ticker
log.Println("开始周期性任务...")
for {
select {
case <-ticker.C:
log.Printf("周期性任务执行: %s", time.Now().Format("15:04:05"))
}
}
}
2. 带退出机制的 Ticker
go
package main
import (
"log"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// 创建带退出信号的ticker
ticker := 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:
return
case t := <-ticker.C:
log.Printf("任务执行: %s", t.Format("15:04:05"))
}
}
}()
log.Println("周期性任务已启动,按 Ctrl+C 停止...")
// 等待退出信号
<-quit
log.Println("收到停止信号...")
// 优雅停止
ticker.Stop()
done <- true
log.Println("周期性任务已停止")
}
3. 带上下文的周期性任务
go
package main
import (
"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("任务被取消")
return
case <-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("程序退出")
}