编程Go 开发者

关于 Go 中 time.Timer 和 time.Ticker 的使用需要知道什么?它们之间的关键区别、正确使用的特点以及在停止/重置时的常见错误是什么?

用 Hintsage AI 助手通过面试

答案

time 包提供了两个有用的工具:TimerTicker

  • Timer 用于一次性延迟(在指定时间后触发一次)。
  • Ticker — 用于定期周期性事件(间隔)。

关键区别:

  • Timer 在触发后会“熄灭”,需要重置 (Reset) 才能再次使用。
  • Ticker 会生成事件,直到显式调用 Stop() 方法停止它。

使用特点:

  • 在工作完成时始终调用 Stop() 以避免资源泄露。
  • 在调用 Stop() 后,可能需要“清空”通道:如果在 Stop() 之前计时器已经触发,则必须从通道中读取。

正确使用 Timer 的示例:

t := time.NewTimer(2 * time.Second) defer t.Stop() // 一定要调用! select { case <-t.C: fmt.Println("时间到!") case <-otherDone: if !t.Stop() { <-t.C // 如果需要,清空通道 } }

带陷阱的问题

在从通道 t.C 获取值后,是否可以立即重新使用 Timer?如果不调用 t.Stop() 会发生什么?

答案: 计时器不应在不调用 Stop() 和可能不读取通道的情况下立刻重置。如果不调用 t.Stop(),可能会留下“死”协程或在下一个循环中出现意外触发(通道未被清空)。正确的做法是调用 Stop(),如果 Stop() 返回 false — 确保通过从 t.C 读取来清空通道。

由于对该主题细节了解不足而导致的实际错误示例


故事

在一家初创公司的通知服务中,重置投递尝试时使用了计时器。触发后立即重置它们而不停止——结果:协程的数量不断增加,进程“泄漏”,系统因内存不足而崩溃。

故事

在一个大型电子商务项目中,用于定期更新商品的 Ticker 在处理程序完成时未停止。即使在删除用户后,处理程序“挂起”,仍然消耗 CPU。

故事

在测试中未调用计时器的 Stop() — 测试随机挂起,因为计时器的通道未被释放,等待计时器的第二个“ping”,而这个“ping”永远不会到来。