time.ticker需配合goroutine使用,避免阻塞导致tick丢失;应加mutex防重入或context控制超时,并注意stop、共享通道、退避等生产问题。

time.Ticker 不能直接并发执行任务
time.Ticker 本身只是按固定间隔发送时间戳的通道,它不启动 goroutine,也不处理并发。如果你在 for range ticker.C 循环里直接调用耗时函数,整个循环会被阻塞,后续 tick 就会堆积或丢失。
常见错误现象:定时任务越跑越慢、漏执行、甚至卡死。
- 必须显式用
go func() { ... }()启动新 goroutine 处理每次 tick - 注意闭包捕获变量问题:别直接在循环里用
for i := range ... { go func() { fmt.Println(i) }() },要用参数传入或定义局部变量 - 如果任务可能超时,建议加
context.WithTimeout控制单次执行生命周期
基础并发 ticker 示例(带防重入保护)
多数场景下,你不想让上次任务还没结束,下次就又触发——这会导致状态冲突或资源竞争。需要简单同步控制。
package main
<p>import (
"fmt"
"sync"
"time"
)</p><p>func main() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()</p><pre class='brush:php;toolbar:false;'>var mu sync.Mutex
var busy bool
for range ticker.C {
mu.Lock()
if busy {
mu.Unlock()
fmt.Println("⚠️ 上次任务还在运行,跳过本次执行")
continue
}
busy = true
mu.Unlock()
go func() {
defer func() {
mu.Lock()
busy = false
mu.Unlock()
}()
fmt.Printf("✅ 开始执行: %s\n", time.Now().Format("15:04:05"))
time.Sleep(3 * time.Second) // 模拟耗时任务
fmt.Printf("✅ 执行完成: %s\n", time.Now().Format("15:04:05"))
}()
}}
立即学习“go语言免费学习笔记(深入)”;
用 context 控制单次任务超时与取消
当任务不可控(比如 HTTP 请求、数据库查询),靠 busy 标志不够——goroutine 可能永远卡住。必须引入 context 实现可中断执行。
-
context.WithTimeout是最常用方式,超时后自动 cancel - 所有支持 context 的标准库函数(如
http.Client.Do、sql.DB.QueryContext)都应传入该 context - 不要忽略
ctx.Err()检查,否则 timeout 不生效
package main
<p>import (
"context"
"fmt"
"time"
)</p><p>func doWork(ctx context.Context) {
select {
case <-time.After(4 * time.Second):
fmt.Println("✅ 任务正常完成")
case <-ctx.Done():
fmt.Printf("❌ 任务被取消: %v\n", ctx.Err())
return
}
}</p><p>func main() {
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()</p><pre class='brush:php;toolbar:false;'>for range ticker.C {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
go func() {
defer cancel()
fmt.Printf("? 启动任务: %s\n", time.Now().Format("15:04:05"))
doWork(ctx)
}()
}
time.Sleep(10 * time.Second) // 给足够时间观察输出}
立即学习“go语言免费学习笔记(深入)”;
生产环境要注意的几个坑
真实服务中,time.Ticker + goroutine 看似简单,但容易在边界条件下崩掉。
-
ticker.Stop()必须调用,否则 goroutine 泄漏(尤其在 long-running service 中) - 不要把
ticker.C直接传给多个 goroutine 共享读取——它不是线程安全的“广播通道”,会 panic - 如果任务失败频率高,考虑加退避逻辑(比如指数退避),避免疯狂重试打挂下游
- 用
runtime.GOMAXPROCS或GOMAXPROCS环境变量确认并发能力,别假设默认值够用
真正难的不是启动 goroutine,而是确保它干净退出、不泄漏、不干扰其他 tick、失败时有可观测性。这些细节往往要靠日志 + pprof + metrics 补全,光靠 ticker 本身做不到。










