Files
cs-note/hzh/GolangStar/Go语言进阶/定时器.md
T

154 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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]]