Golang学习笔记:定时crontab

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("程序退出")
}
相关推荐
q***18848 小时前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
开发语言·后端·golang
码上淘金19 小时前
在 YAML 中如何将 JSON 对象作为字符串整体赋值?——兼谈 Go Template 中的 fromJson 使用
java·golang·json
小生凡一1 天前
图解|Go语言实现 Agent|LLM+MCP+RAG
开发语言·后端·golang
pipip.1 天前
Go原生高性能内存网关IMS,比Redis更快
开发语言·redis·golang
q***06291 天前
环境安装与配置:全面了解 Go 语言的安装与设置
开发语言·后端·golang
月屯1 天前
平台消息推送(go)
数据库·后端·golang·cocoa·iphone·gin
百锦再2 天前
第11章 泛型、trait与生命周期
android·网络·人工智能·python·golang·rust·go
hweiyu002 天前
Go Fiber 简介
开发语言·后端·golang
周杰伦_Jay2 天前
【智能体(Agent)技术深度解析】从架构到实现细节,核心是实现“感知环境→处理信息→决策行动→影响环境”的闭环
人工智能·机器学习·微服务·架构·golang·数据挖掘