每个启动的Goroutine必须有明确可控的退出路径;须用context控制生命周期、监听ctx.Done(),channel使用需闭环,定时器要显式Stop,并通过pprof和NumGoroutine监控验证。

预防 Goroutine 泄漏,核心就一条:每个启动的 Goroutine,必须有明确、可控的退出路径。它不是“写完再看”,而是“启动前就想好怎么停”。
用 context 控制生命周期
所有可能长期运行或阻塞的 Goroutine,都应接收一个 context.Context,并在关键阻塞点监听 ctx.Done()。
- 不要裸写
time.Sleep或直接;改用 <code>select包裹,必含分支 - HTTP 客户端、数据库查询等第三方调用,务必使用带 context 的方法(如
http.Client.Do(req.WithContext(ctx))、db.QueryContext) - 启动时用
context.WithCancel或context.WithTimeout,主流程结束前调用cancel()
Channel 使用必须闭环
Channel 不是“发完就不管”的管道,而是一条双向契约:谁发、谁收、谁关,必须提前约定清楚。
- 单一发送者场景:由发送方负责
close(ch);接收方用for v := range ch自动退出 - 多发送者场景:禁止关闭数据 channel;改用独立的
stopCh chan struct{}通知所有 goroutine 退出 - 避免向无缓冲 channel 发送数据却不确保有人接收;有缓冲 channel 要预估容量,防止写满后永久阻塞
定时任务别用 Ticker 硬扛
time.NewTicker 创建的 goroutine 不会随父逻辑自动结束,极易遗漏 ticker.Stop() 导致泄漏。
立即学习“go语言免费学习笔记(深入)”;
- 优先用
time.AfterFunc实现“一次性延迟执行”,无需手动清理 - 若必须轮询,确保
ticker.Stop()在 goroutine 退出前被调用(建议 defer) - 在
select中同时监听ticker.C和ctx.Done(),收到取消信号立即 break 并 stop
验证与监控不能少
靠肉眼很难发现泄漏,要建立轻量但有效的观测习惯。
- 测试中用
runtime.NumGoroutine()前后对比,尤其关注单元测试前后差值 - 生产服务开启
/debug/pprof,定期访问/debug/pprof/goroutine?debug=2查看阻塞堆栈 - 重点关注状态为
chan receive、semacquire、select的 goroutine,它们大概率已卡住


















