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

4.1 KiB
Raw Blame History

tags, create time
tags create time
go
golang
定时器
Timer
Ticker
并发
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 控制生命周期

关联笔记