4.1 KiB
4.1 KiB
tags, create time
| tags | create time | ||||||
|---|---|---|---|---|---|---|---|
|
2026-06-07 15:05 |
定时器
概述
Go 标准库提供了 time.Timer(一次性定时器)和 time.Ticker(周期性定时器),它们是构建超时控制、心跳检测、定时任务等并发模式的基础工具。
正文
Timer — 一次性定时器
[!question] 💭 思考 如何在一个 goroutine 中等待指定时间后执行某个操作?用
time.Sleep?但如果需要提前取消呢?
timer := time.NewTimer(2 * time.Second) // 2秒后触发
<-timer.C // 阻塞直到触发
fmt.Println("timeout!")
Timer 的核心是 C <-chan Time——一个在触发时刻写入当前时间的 channel。
停止与重置
timer := time.NewTimer(5 * time.Second)
// 尝试在触发前停止
if !timer.Stop() {
<-timer.C // 如果已经触发,需要从 C 中读出值
}
// 重置定时器
timer.Reset(3 * time.Second) // 3秒后重新触发
[!warning] ⚠️ Stop/Reset 的返回值陷阱
Stop()返回true:定时器还未触发,成功停止Stop()返回false:定时器已触发,C 通道中可能已有值当
Stop()返回 false 时,如果后续代码仍需读取 timer.C,必须先消费掉那个值,否则会导致泄漏。
[!tip] 💡 安全使用模式
timer := time.NewTimer(d) select { case <-timer.C: // 超时处理 case <-someChannel: timer.Stop() if !timer.Stop() { <-timer.C // 清理残留值 } }
AfterFunc — 延迟执行回调
// 1秒后执行 f,返回 Timer 以便取消
timer := time.AfterFunc(1*time.Second, func() {
fmt.Println("after 1 second")
})
// 如果需要取消
timer.Stop()
After — 便捷超时
select {
case result := <-doWork():
fmt.Println("完成:", result)
case <-time.After(5 * time.Second):
fmt.Println("超时!")
}
[!note] 📝 After 的实现
time.After(d)等价于time.NewTimer(d).C。它创建了一个内部 Timer,但无法提前释放——所以长生命周期场景推荐手动管理 Timer。
Ticker — 周期性定时器
Ticker 每隔固定间隔向通道发送信号,适用于轮询、心跳等场景:
ticker := time.NewTicker(1 * time.Second)
// 务必在退出时停止
defer ticker.Stop()
for tick := range ticker.C {
fmt.Println("tick at", tick)
// 如果只需要执行一次
break
}
[!warning] ⚠️ Ticker 的资源泄漏
ticker.Stop()只停止发送新信号,不会关闭 ticker.C 通道- 必须通过其他方式(如 context.Done / break)退出循环,否则 goroutine 泄漏
func periodicWork(ctx context.Context, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop() // ✅ 确保释放资源
for {
select {
case <-ticker.C:
doWork()
case <-ctx.Done():
return // ✅ 正常退出
}
}
}
常见模式对比
| 需求 | 推荐方案 | 说明 |
|---|---|---|
| 等待 N 秒后执行一次 | time.After |
简洁,适合短生命周期 |
| 可取消的延迟执行 | time.AfterFunc |
返回 Timer 可调用 Stop |
| 周期性执行 + 可控退出 | Ticker + context |
灵活,资源可控 |
| 精确单次触发 | time.NewTimer |
可 Stop/Reset,资源可控 |
精度问题
[!info] ℹ️ 补充知识
- Timer/Ticker 的精度依赖于操作系统调度,不是严格精确的
- 在高精度场景中,应设置缓冲避免错过 tick:
time.NewTicker(d)如果消费跟不上会产生积压- Ticker 消费不及时时,多余的 tick 会被丢弃(通道满时阻塞发送方)
// ❌ 危险:无缓冲,消费慢时会阻塞
ticker := time.NewTicker(1 * time.Millisecond)
// ✅ 推荐:有缓冲,允许短暂积压
ticker := time.NewTicker(1 * time.Millisecond)
// 配合 context 控制生命周期