--- tags: [go, golang, 定时器, Timer, Ticker, 并发] create time: 2026-06-07 15:05 --- # 定时器 ## 概述 Go 标准库提供了 `time.Timer`(一次性定时器)和 `time.Ticker`(周期性定时器),它们是构建超时控制、心跳检测、定时任务等并发模式的基础工具。 ## 正文 ### Timer — 一次性定时器 > [!question] 💭 思考 > 如何在一个 goroutine 中等待指定时间后执行某个操作?用 `time.Sleep`?但如果需要提前取消呢? ```go timer := time.NewTimer(2 * time.Second) // 2秒后触发 <-timer.C // 阻塞直到触发 fmt.Println("timeout!") ``` `Timer` 的核心是 `C <-chan Time`——一个在触发时刻写入当前时间的 channel。 #### 停止与重置 ```go 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] 💡 安全使用模式 > ```go > timer := time.NewTimer(d) > select { > case <-timer.C: > // 超时处理 > case <-someChannel: > timer.Stop() > if !timer.Stop() { > <-timer.C // 清理残留值 > } > } > ``` #### AfterFunc — 延迟执行回调 ```go // 1秒后执行 f,返回 Timer 以便取消 timer := time.AfterFunc(1*time.Second, func() { fmt.Println("after 1 second") }) // 如果需要取消 timer.Stop() ``` #### After — 便捷超时 ```go 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 每隔固定间隔向通道发送信号,适用于轮询、心跳等场景: ```go 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 泄漏 ```go 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 会被丢弃(通道满时阻塞发送方) ```go // ❌ 危险:无缓冲,消费慢时会阻塞 ticker := time.NewTicker(1 * time.Millisecond) // ✅ 推荐:有缓冲,允许短暂积压 ticker := time.NewTicker(1 * time.Millisecond) // 配合 context 控制生命周期 ``` ## 关联笔记 - [[hzh/GolangStar/Go语言进阶/Select]] - [[hzh/GolangStar/Go语言进阶/Context]] - [[hzh/GolangStar/Go语言进阶/Goroutine]]