timer只触发一次,本质是单次倒计时器,启动后仅向其c字段(chan time.time)发送一次信号便停止,不可当作循环定时器使用。

Timer 只触发一次,别误当成循环定时器用
Go 的 time.Timer 本质是单次倒计时器,启动后只发送一次信号到 C 字段(一个 chan time.Time),之后就停了。很多人写成 timer := time.NewTimer(1 * time.Second); ,以为能反复触发,结果程序卡住或漏触发。
正确做法是:需要重复执行时,必须手动重置(Reset)或新建。但注意:Reset 在 timer 已过期且 C 未被接收时会返回 false,此时需先 drain 掉旧的 C(用 select 非阻塞读),否则可能 panic 或逻辑错乱。
- 不要在
for循环里反复调用time.NewTimer,频繁创建对象有 GC 开销 - 若只是“延迟执行一次”,用
time.AfterFunc更简洁;若需取消,保留Timer实例并调用Stop() -
Timer.Reset()不是线程安全的,不能在多个 goroutine 里并发调用
Ticker 是真正的周期性定时器,但别忘了 Stop
time.Ticker 才是为周期任务设计的,它内部维护一个 goroutine 持续往 C 发送时间戳。常见错误是启用了 time.NewTicker(500 * time.Millisecond) 却没在不再需要时调用 ticker.Stop() —— 这会导致 goroutine 泄漏,且 C 缓冲区持续堆积(默认缓冲 1),最终卡死或内存增长。
典型场景如 HTTP handler 中启了个 Ticker 做轮询,handler 结束却没 stop,下次请求又起一个,泄漏雪球越滚越大。
- 务必在作用域结束前显式调用
ticker.Stop(),尤其在 defer 中最稳妥 -
Ticker的间隔是“最小间隔”,实际触发时间可能因调度延迟略晚,不适用于高精度实时控制 - 如果只是想每 N 秒做一次、且每次执行耗时可能超过 N 秒,用
Timer.Reset替代Ticker更安全,避免并发冲突
Timer 和 Ticker 的底层复用机制影响性能选择
Go 运行时对 Timer 和 Ticker 共享同一套时间轮(timing wheel)实现,但它们的生命周期管理完全不同:Timer 是惰性插入、过期即删;Ticker 是常驻注册、靠 goroutine 主动推送。这意味着:
- 大量短间隔
Ticker(比如毫秒级)会显著增加调度器负担,而同频次的Timer.Reset虽然代码稍繁,但更轻量 - Go 1.14+ 对小间隔
Timer做了优化,但Ticker仍固定占用一个 goroutine,哪怕你只读一次C - 如果定时逻辑本身带状态(比如要累计次数、判断超时),优先用
Timer+ 循环控制,比依赖Ticker的外部状态更可控
常见 panic 场景:C channel 关闭后继续读
Timer.Stop() 和 Ticker.Stop() 都不会关闭其 C channel,只是停止发送。但如果你手动 close(ticker.C) 或在 Stop() 后还对 C 做无保护接收(比如 ),就会 panic:<code>panic: send on closed channel 或 deadlock(因 C 永远不发新值)。
- 永远不要 close
timer.C或ticker.C—— 它们由 runtime 管理,close 属于未定义行为 - Stop 后再读
C是安全的,但会永久阻塞;要用select加default或timeout防卡死 - 检查是否已 Stop 的唯一可靠方式是记录布尔标志位,而不是依赖
C是否可读
复杂点在于:Timer/Ticker 的生命周期往往和业务逻辑耦合,比如 WebSocket 心跳、数据库连接保活,这时候容易忽略 Stop 的时机——不是“任务做完就 Stop”,而是“资源释放时才 Stop”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











