go中“goroutine内存泄漏”指goroutine卡在select、sleep、channel操作等处不退出,持续占用栈、调度开销及闭包变量,导致gc无法回收;最常见于channel发送阻塞、receiver未close、ticker/timer未stop、http/db未用context、闭包误捕大对象等场景。

Go 里所谓“goroutine 内存泄漏”,不是堆内存没被 GC 回收,而是 goroutine 卡在 select、time.Sleep、ch 或 <code>http.Get 上永不返回——它持续占着栈(至少 2KB)、调度器跟踪开销、以及闭包捕获的所有变量,这些对象全不能被 GC。
goroutine 卡在 channel 操作上不退出
这是最常见泄漏点:无缓冲 channel 发送阻塞、有缓冲 channel 满了没人收、receiver 提前退出但 sender 还在发。
- 向
ch发送前,必须用select包一层,带default或ctx.Done()分支,避免永久挂起 - receiver 不要只写
for range ch就完事——得确认 sender 真的会close(ch);否则range永远等不到 EOF - 谁创建
ch,谁负责close();向已关闭的 channel 发送会 panic,接收则立即返回零值 - 错误示例:
go func() { for i := 0; ; i++ { ch —— 第一次发送就卡死,<code>i和闭包环境全锁在内存里
time.Ticker / time.Timer 忘记 stop
time.NewTicker 和 time.NewTimer 返回的对象底层持有运行时计时器资源,不显式 Stop() 就算 goroutine 退出了,资源也不会释放。
-
defer ticker.Stop()必须写,且要写在 goroutine 函数体开头附近,不能依赖 “反正有 context 控制” - 别用
time.After替代time.NewTimer做长周期等待——time.After创建的 timer 不可回收,哪怕你 never 读它,也会一直占着资源直到超时触发 - 正确写法:
ticker := time.NewTicker(1 * time.Second); defer ticker.Stop(); for { select { case
HTTP handler 或数据库查询没传 context
裸调 http.Get、db.Query、io.Read 等 I/O 操作,一旦网络卡住或服务无响应,goroutine 就永远停在 syscall 上,既不响应 cancel,也不释放连接。
- 所有 I/O 必须用支持 context 的变体:
http.DefaultClient.Do(req.WithContext(ctx))、db.QueryContext(ctx, sql)、conn.SetReadDeadline配合ctx.Done()轮询 -
defer resp.Body.Close()不够——如果resp根本没拿到(比如请求卡在 DNS 或 TCP 握手),Close()压根不会执行;要用ctx控制整个请求生命周期 - 第三方库也要查文档:比如 redis-go 的
Do方法不支持 context,得换DoContext
闭包捕获外层变量延长生命周期
goroutine 在闭包中捕获了本不该长期存活的变量(比如大 slice、map、结构体指针),而该 goroutine 又迟迟不退出,此时即使外部作用域早已结束,这些变量仍被闭包隐式引用,无法被 GC 回收。
- 避免在
go func() { }()中直接使用外层循环变量,如for i := range items { go func() { use(i) }() }—— 所有 goroutine 共享同一个i地址 - 正确写法是显式传参:
for i := range items { go func(idx int) { use(idx) }(i) },或用局部变量绑定:for i := range items { idx := i; go func() { use(idx) }() } - 若闭包需访问结构体字段,优先传值或只传必要字段,而非整个结构体指针;尤其警惕
*http.Request、*sql.DB等重型对象被无意捕获 -
defer+ 闭包组合容易导致 context 或连接泄漏:清理逻辑尽量不依赖闭包捕获,改用显式参数传递,例如defer closeConn(conn)
真正难防的是那些“看起来能退出,其实卡在某处”的场景:比如 receiver 没检查 ok 就继续处理,或 select 里把 ctx.Done() 放在非首位置导致取消信号被忽略。这类问题在线上压测或低流量时段才暴露,调试成本远高于写代码时多加一行判断。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











