跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何在Golang中避免goroutine泄漏_Golang并发编程中goroutine泄漏防范

goroutine泄漏典型表现为内存持续上涨且runtime.NumGoroutine()只增不减;可通过pprof查看goroutine profile,若大量goroutine阻塞在select、chan receive或semacquire,则大概率因channel未关闭或未消费导致。 goroutine泄漏的典型表现和快速确认方法 程序内存持续上涨、
runtime.NumGoroutine()
返回值只增不减,且没有明显业务增长时,基本可以判定存在 goroutine 泄漏。更直接的方式是用
pprof
查看 goroutine profile:
curl 'http://localhost:6060/debug/pprof/goroutine?debug=2'
如果输出中大量 goroutine 停留在
select
chan receive
semacquire
状态,大概率是阻塞在 channel 操作或锁上。 channel 未关闭或未消费导致的泄漏 向一个无缓冲 channel 发送数据,但没有 goroutine 接收,发送方会永久阻塞;有缓冲 channel 若容量填满后继续发送,同样阻塞。这类 goroutine 无法被调度器回收。 避免无缓冲 channel 的单向使用:如只启动 sender 不启 receiver,或 receiver 提前退出而 sender 不知情 用
select
+
default
防止无限等待:
select {
case ch <- data:
default:
// 丢弃或重试,避免阻塞
}
对必须保证送达的场景,用带超时的
select
select {
case ch <- data:
case <-time.After(5 * time.Second):
log.Println("send timeout")
}
receiver 退出前,确保 sender 能感知 —— 通常靠额外的
done
channel 或 context 取消 context 被忽略或未传递到所有子 goroutine 只要 goroutine 中涉及 I/O、channel 操作或循环等待,就必须接收
ctx
并监听
ctx.Done()
。漏掉任意一层,就可能让 goroutine 在父任务结束之后继续运行。 启动 goroutine 时,显式传入
ctx
,不要依赖闭包捕获外层变量(容易误用已取消的 context) 所有阻塞操作都应支持 cancel:用
ctx.Done()
替代空
select{}
,用
http.NewRequestWithContext(ctx, ...)
替代裸
http.NewRequest
注意第三方库是否真正响应
ctx
—— 比如某些数据库驱动的
QueryContext
是必须显式调用的,
Query
不会受 context 控制 timer 和 ticker 未停止引发的隐性泄漏
time.Ticker
time.Timer
启动后会持续持有 goroutine,即使所属逻辑已退出。它们不会因为变量超出作用域而自动 stop。 立即学习 “ go语言免费学习笔记(深入) ”; 每次调用
time.NewTicker
time.NewTimer
后,务必在对应逻辑结束时调用
ticker.Stop()
timer.Stop()
select
中监听
ctx.Done()
时,要确保
Stop()
被执行 —— 注意 defer 不会在 panic 后执行,建议用
if !ticker.Stop() { 清空可能 pending 的 tick
避免在循环中反复创建 ticker:应复用并控制启停,而不是“每轮 new 一个” 最易被忽略的是组合场景:比如一个 goroutine 同时读 channel、等 timer、发 HTTP 请求,三者都得各自处理 cancel,缺一不可。少监听一个
ctx.Done()
,就等于埋下一颗泄漏定时炸弹。

相关文章