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

154 lines
4.1 KiB
Markdown
Raw Normal View History

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