go中用ticker+channel实现并发定时器的核心是:ticker产生周期时间信号,channel控制启停与参数传递,goroutine实现非阻塞可取消任务;需调用Stop防泄漏,用done或context优雅终止,多任务应独立ticker和channel。

使用 Go 的 ticker 和 channel 组合实现并发定时器,核心在于:用 time.Ticker 产生周期性时间信号,用 channel 控制启停、传递任务参数或接收结果,再配合 goroutine 实现非阻塞、可取消、可扩展的周期性任务管理。
基础结构:Ticker + Goroutine 启动周期任务
time.Ticker 会按固定间隔向其 C 字段(一个 chan time.Time)发送当前时间。启动一个 goroutine 监听该 channel,即可执行周期逻辑:
ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() go func() { for range ticker.C { fmt.Println("执行周期任务:", time.Now()) // 这里放你的业务逻辑,如采集指标、清理缓存等 } }()
注意:必须调用 ticker.Stop() 防止 goroutine 泄漏;for range ticker.C 是标准写法,自动处理 channel 关闭。
支持动态启停:用 done channel 控制生命周期
直接关闭 ticker.C 不安全,应通过额外的 done channel 控制循环退出:
立即学习“go语言免费学习笔记(深入)”;
示例:
func runPeriodicTask(interval time.Duration, done chan struct{}) { ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ticker.C: fmt.Println("任务运行中...") // 执行具体工作(建议加 recover 防 panic 中断) case <-done: fmt.Println("任务已停止") return } }
}
// 使用 done := make(chan struct{}) go runPeriodicTask(3*time.Second, done)
// 停止时 close(done)
并发多任务 + 参数隔离:为每个任务分配独立 channel
若需同时运行多个不同周期、不同参数的任务(例如:每 10s 检查磁盘,每 30s 上报日志),不要共用一个 ticker。应为每个任务创建独立 ticker 和控制 channel:
- 每个任务封装成函数,接收 interval、参数、done channel 等
- 用 map 或 slice 管理多个任务的
donechannel,便于统一停止 - 避免在周期函数内做耗时同步操作(如 http 请求),必要时起新 goroutine 或用带超时的 context
简单多任务管理示意:
type Task struct { Name string Interval time.Duration Action func() Done chan struct{} } func (t *Task) Start() { ticker := time.NewTicker(t.Interval) go func() { defer ticker.Stop() for { select { case <-ticker.C: t.Action() case <-t.Done: return } } }() }
// 启动多个 tasks := []Task{ {Name: "disk-check", Interval: 10 time.Second, Action: checkDisk}, {Name: "log-report", Interval: 30 time.Second, Action: reportLogs}, }
for i := range tasks { tasks[i].Done = make(chan struct{}) tasks[i].Start() }
// 停止全部 for _, t := range tasks { close(t.Done) }
进阶:集成 context 实现超时与取消传播
当周期任务内部含 I/O 操作(如数据库查询、HTTP 调用),推荐用 context.Context 替代裸 done channel,获得超时、截止时间、父子取消链等能力:
- 用
context.WithTimeout或context.WithCancel包装任务上下文 - 在 select 中监听
ctx.Done(),而非自定义 done channel - 任务函数内所有阻塞操作(如
http.Client.Do)应接受该 ctx
示例片段:
func runWithCtx(ctx context.Context, interval time.Duration, work func(context.Context)) { ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ticker.C: // 每次执行都传入新衍生的子 context(可设单次超时) childCtx, cancel := context.WithTimeout(ctx, 2*time.Second) work(childCtx) cancel() case <-ctx.Done(): return } }
}
不复杂但容易忽略:Ticker 的精度受系统调度和 GC 影响,不适合毫秒级强实时场景;高频任务建议用 time.AfterFunc 链式重启,或引入更专业的调度库(如 asynq、gocron)处理复杂依赖与持久化需求。