必须调用 timer.stop():即使 timer 已触发或 timer.c 已读取,不显式 stop 会导致 goroutine 和 channel 泄漏,gc 无法回收,压测时 goroutine 数持续增长。

timer.Stop() 必须在 timer 触发后也调用
很多人误以为 timer 已触发(timer.C 已被接收)就不用 timer.Stop() 了,其实不然。Go 的 time.Timer 底层持有 goroutine 和 channel,即使已触发,若不显式 Stop(),仍可能保留对 channel 的弱引用,导致 GC 无法回收相关资源;压测时 goroutine 数缓慢上涨,pprof 中可见残留的 runtime.timerproc。
正确做法是:无论是否已触发、是否已读 timer.C,只要不再需要该 timer,就立即调用 timer.Stop()。
- 错误写法:
select { case —— 缺少 <code>timer.Stop() - 正确写法:
select { case - 更稳妥写法(尤其配合 defer):
defer func() { if !timer.Stop() { ,用于处理已触发但 channel 尚未读完的情况
Reset 前必须先 Stop 并检查返回值
timer.Reset() 不是原子安全操作。如果 timer 已触发(timer.C 已关闭),直接 Reset() 会成功返回 true,但底层旧 goroutine 仍可能往已关闭的 channel 发送时间值 → panic: send on closed channel。
唯一安全的重用模式是:
- 始终先调
timer.Stop(),并检查其返回值 - 仅当
timer.Stop()返回false(说明已触发或已 stop),才需额外消费一次timer.C(避免阻塞) - 再调
timer.Reset(newDur)
示例:
if !timer.Stop() {
select {
case <h3>在 select 中监听 timer.C 时 Stop 的时机很关键</h3><p>常见陷阱:把 <code>timer.Stop()</code> 写在 <code>select</code> 的某个 <code>case</code> 分支里,而其他分支(比如 <code>ctx.Done()</code>)退出时漏掉 Stop。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件"><img
src="https://img.php.cn/upload/webcode/000/000/164/636a2b4d84031727.png" alt="使用Go语言搭建家庭相册系统-相关课件" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件" class="overflowclass">使用Go语言搭建家庭相册系统-相关课件</a>
<p class="overflowclass">使用Go语言搭建家庭相册系统-相关课件</p>
</div>
<a rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>正确姿势是让 <code>Stop()</code> 脱离分支逻辑,确保无论从哪个出口离开,timer 都被清理:</p>
- 定义 timer 在函数作用域顶部,用
defer timer.Stop()(注意:panic 时 defer 不执行,慎用于关键路径) - 更健壮的做法:在所有可能的退出路径前显式调用
timer.Stop(),包括return、break、goto或 context 取消分支 - 若 timer 生命周期与 context 绑定,务必在
case 分支末尾加 <code>timer.Stop()
time.After 和 time.AfterFunc 不需要手动 Stop
time.After(d) 返回的是一个 <chan time.time></chan>,它内部封装了 time.Timer,且在触发后自动清理;time.AfterFunc(d, f) 同理,函数执行完即释放资源。这两者都不暴露 timer 实例,因此不存在忘记 Stop() 的问题。
但它们也不支持取消重置、无法参与多路复用 select(除非你只关心超时)、也不能获取触发时刻。需要这些能力时,必须退回到 time.NewTimer + 手动 Stop() 的组合。
一句话判断:只做“等 X 秒后干件事”,优先用 time.AfterFunc;要控制生命周期、参与 select、动态调整时长,就必须亲手管好 timer.Stop()。
真正容易出事的,从来不是第一次创建 timer,而是 long-running 服务中反复 Reset() 却没人检查 Stop() 返回值、也没清空已触发的 timer.C 缓冲 —— 这类 bug 往往上线后数周才在低概率并发场景下暴露。










