应优先使用 time.after;若需中途取消或重置延时,必须用 time.newtimer。time.after 返回只读通道无法停止,time.newtimer 返回可操作指针支持 stop() 和 reset()。

Go 里没有“定时器对象”这种东西,只有 time.Timer 和 time.Ticker 两个结构体,它们背后共用同一套底层调度机制,但行为和生命周期完全不同——混用或误停会导致 goroutine 泄漏、通道阻塞、时间漂移。
什么时候该用 time.NewTimer 而不是 time.After
两者都适合一次性延时,但语义和资源管理差异很大:time.After 返回的是一个只读通道,无法主动停止;time.NewTimer 返回的是可操作的指针,支持 Stop() 和 Reset()。
- 如果你只是简单等待几秒后执行某事,且确定不会中途取消(比如日志打点、测试 sleep),用
更简洁 - 如果涉及超时控制(如 HTTP 客户端等待响应)、可能被外部中断(如用户取消请求)、或需复用同一 timer 实例,必须用
time.NewTimer,否则超时后通道仍会发送时间值,导致后续select意外命中 -
time.After每次调用都会新建一个 timer 并注册进系统,高频调用(如每毫秒一次)会堆积未触发的 timer,增加 runtime 调度负担
time.Ticker 必须手动 Stop(),否则 goroutine 永不退出
time.NewTicker 启动后,底层会持续向 ticker.C 发送时间值,即使你没在读——这个 goroutine 不会自动结束。常见错误是只 defer ticker.Stop() 却忘了它只在函数返回时生效,而 ticker 可能已在后台跑了几分钟。
- 在循环中使用
ticker.C时,务必用select配合done通道做退出控制,不能只靠break - 不要在
for range ticker.C中直接 return 或 panic,这会让 ticker goroutine 悬挂 - 如果 ticker 用于健康检查等长周期任务,建议在
Stop()前先关闭接收方通道(如close(done)),避免select漏判 - 企业级代码中,常把
ticker封装进 struct,并在Stop()方法里同时关 channel 和调用ticker.Stop()
Reset() 的坑:只对未触发/已停止的 timer 有效
timer.Reset(d) 不是“重新开始倒计时”,而是“取消当前计时 + 新建一个计时”。它返回 bool:true 表示原 timer 还没触发,可以安全重置;false 表示它已经触发过(或正在触发中),此时重置无效,且 timer.C 里可能还留着旧时间值。
- 常见错误:在
select收到timer.C后立刻调用Reset(),结果发现没反应——因为此时 timer 已触发,Reset()返回 false,且不会清空通道 - 正确做法是先
Stop(),再Reset();或者更稳妥地,每次触发后都Stop(),然后time.NewTimer()新建一个 -
Reset()不能用于time.Ticker,它的对应方法是ticker.Reset(d)(Go 1.15+),但注意这会丢弃已排队但未读取的 tick
最易被忽略的一点:所有 timer/ticker 的底层都依赖 Go runtime 的全局定时器堆,频繁创建销毁(尤其 sub-millisecond 级别)会显著抬高 GC 压力和调度延迟。真有高频定时需求,优先考虑基于 time.Sleep 的 busy-wait 循环或自研时间轮,而不是堆砌 NewTimer。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











