Go time.Ticker 停止后清理 goroutine
来源:17golang原创
时间:2026-10-31 13:14:39 404浏览 收藏
Go 里的 time.Ticker 停止后,真正需要清理的通常不是某个“隐藏 goroutine”,而是你自己启动的消费 goroutine。ticker.Stop() 只负责停止后续 tick,不会关闭 ticker.C,也不会让正在执行或阻塞等待的 goroutine 自动退出。要让周期任务干净结束,应把 ticker 的停止和 goroutine 的退出信号分开管理:用 defer ticker.Stop() 收尾资源,用 context.Done() 或专用 done channel 结束循环。
Stop停止送出新 tick,但不关闭C,不能单独解决 goroutine 泄漏。- 消费循环必须同时监听
ticker.C和退出信号,调用方负责触发取消。 - 慢处理、
Reset、Go 1.23 的 GC 行为都会影响清理策略,但不会改变“协作式退出”的原则。
先把 Ticker、channel 和 goroutine 分开看
time.NewTicker 返回一个带有只读 channel 的 ticker。Stop 之后不再发送 tick,但官方文档明确说明它不会关闭 channel;这样既能避免并发读取者看到一个人为关闭造成的“结束”,也保留了之后调用 Reset 的可能性。因此,下面这种循环没有退出条件:
ticker := time.NewTicker(time.Second)
defer ticker.Stop() // 只负责停止 ticker,不负责结束下面的 goroutine
go func() {
for range ticker.C {
refreshCache()
}
}()
调用外层函数返回后,defer 会执行 Stop,但 goroutine 仍在等待一个永远不会再到来的值。这里的“清理 goroutine”必须由 goroutine 自己观察退出信号完成;不要把“停止 ticker”误写成“关闭 channel”。

用 context 让周期消费循环协作退出
更稳妥的写法是把取消权交给调用方,并让 goroutine 在同一个 select 里等待 tick 和取消信号。defer ticker.Stop() 保证函数离开时不再保留周期触发器,ctx.Done() 则负责让循环返回:
func runRefresh(ctx context.Context, interval time.Duration, refresh func(context.Context) error) {
ticker := time.NewTicker(interval)
defer ticker.Stop() // 函数退出时停止后续 tick
for {
select {
case
这个结构的关键不是 Stop 的调用位置,而是两个生命周期有明确的负责人:ticker 在 runRefresh 内部结束,goroutine 由 ctx 协作退出。若 refresh 本身可能阻塞,它也应使用这个 context;否则外层循环退出了,当前工作仍可能继续占用资源。
| 对象 | 结束方式 | 不能替代什么 |
|---|---|---|
*time.Ticker | ticker.Stop() | 不能关闭 C,不能杀死 goroutine |
| 消费 goroutine | ctx.Done() 或 done channel | 不能假设 ticker 停止后会自动返回 |
| 当前刷新任务 | 让任务使用 context 协作取消 | 不能依赖 Stop 强行中断函数调用 |

慢处理和 Reset 场景要单独判断
Ticker 不是任务队列。接收方处理较慢时,ticker 会调整发送间隔或丢弃 tick 以跟上时间节奏,所以不能用收到的次数推断每个时间点都执行过。若每次刷新必须串行且不能丢任务,应考虑显式队列,而不是把 Ticker 当补偿机制。
需要复用同一个 ticker 时,Reset 会先停止旧周期,再用新周期安排下一次 tick;它仍然不负责结束消费 goroutine。只有在循环仍由同一套退出信号管理时才适合复用。Go 1.23 起,已经不可达且未停止的 ticker 可以被垃圾回收,显式 Stop 仍然值得保留,因为它表达了业务生命周期,并能让周期任务立即停止产生新 tick。
常见问题
Stop 之后可以直接关闭 ticker.C 吗?
不可以。ticker.C 由 time 包管理,调用方不应关闭它;用独立的 context 或 done channel 表示消费循环结束。
只写 defer ticker.Stop() 是否足够?
如果没有自己启动消费 goroutine,通常足够表达 ticker 的资源收尾;一旦启动了长期消费循环,还必须给循环增加退出分支。
Stop 能中断已经开始的刷新函数吗?
不能。它只停止后续 tick。刷新函数是否能提前结束,取决于它是否把 context 传到 I/O、重试或等待操作中。
-
365 收藏
-
427 收藏
-
172 收藏
-
438 收藏
-
232 收藏
-
252 收藏
-
460 收藏
-
164 收藏
-
197 收藏
-
176 收藏
-
251 收藏
-
159 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习