Go 的 goroutine 泄漏:几种最常见的写法

举报
yd_271078175 发表于 2026/11/10 08:27:12 2026/11/10
【摘要】 goroutine 泄漏不像内存泄漏那样慢慢涨,它可能在一个循环里几分钟就堆到几万个,然后进程被 OOM 或者调度器拖垮。更麻烦的是它不会报错,只会让程序越来越慢。

goroutine 泄漏不像内存泄漏那样慢慢涨,它可能在一个循环里几分钟就堆到几万个,然后进程被 OOM 或者调度器拖垮。更麻烦的是它不会报错,只会让程序越来越慢。

这篇列几种最常见的泄漏写法,以及怎么在代码里提前避开。

一、没有退出条件的 for

go func() {
    for {
        doSomething()
        time.Sleep(time.Second)
    }
}()

这段代码的问题不是 for,是它没有任何机会停下来。如果这个 goroutine 是随某个对象创建的,对象销毁了它还在跑,每创建一次就泄漏一个。

要给它两条出路:一个 stop channel,或者一个 context:

go func() {
    t := time.NewTicker(time.Second)
    defer t.Stop()
    for {
        select {
        case <-ctx.Done():
            return
        case <-t.C:
            doSomething()
        }
    }
}()

select 里必须有 ctx.Done() 这一支,否则取消信号传不进来。这不是格式问题,是唯一能让它退出的路径。

二、往 channel 发数据,但没人接

ch := make(chan int) // 无缓冲
go func() { ch <- 1 }()
// 忘了读,或者提前 return 了

无缓冲 channel 的发送会阻塞,直到有人接收。如果接收方因为别的原因退出了,发送方就永远卡在那里。

有缓冲的也一样,只是能多撑几个:

ch := make(chan int, 1)
go func() { ch <- 1; ch <- 2 }() // 第二个就阻塞了

稳妥的写法是发送时也 select 一个取消分支:

select {
case ch <- v:
case <-ctx.Done():
    return
}

或者干脆用带缓冲、容量明确的 channel,并保证接收方一定会读到关闭。

三、Ticker 忘了 Stop

t := time.NewTicker(time.Second)
// 用完没有 t.Stop()

Ticker 内部是一个独立的时间轮,不 Stop 就一直活着,还会持续往 channel 里发时间。短命进程无所谓,常驻服务里每个请求创建一个 Ticker 就是灾难。

习惯写法:

t := time.NewTicker(time.Second)
defer t.Stop()

time.After 也有类似问题:它在定时器触发前不会被回收。循环里反复 time.After 会堆积大量定时器,这种情况用 Ticker 或者带 ctx 的 time.NewTimer。

四、HTTP 响应体没关

resp, err := http.Get(url)
if err != nil {
    return err
}
// 忘了 resp.Body.Close()

不关 Body 不只是句柄泄漏,还会让底层连接无法复用,连接池很快耗尽,上层看到的是一堆超时。

正确写法是拿到响应立刻 defer 关闭,并且读完再关:

defer resp.Body.Close()
io.Copy(io.Discard, resp.Body)

如果只关心状态码不关心内容,也要读完再丢弃,否则连接会被直接断掉而不是复用。

五、怎么发现泄漏

最简单的办法是在测试或压测里数一下 goroutine 数量:

before := runtime.NumGoroutine()
doSomething()
time.Sleep(100 * time.Millisecond)
fmt.Println("goroutine 变化:", runtime.NumGoroutine()-before)

如果重复执行几百次,数字一直往上涨,基本可以确定有泄漏。

更细的可以用 pprof:

import _ "net/http/pprof"

然后访问 /debug/pprof/goroutine?debug=2,能看到每个 goroutine 卡在哪一行。卡在 chan send 或 chan receive 的,多半是第二类问题。

六、小结

四条最常见的泄漏:

1. for 没有退出条件; 2. 往没人接收的 channel 发数据; 3. Ticker 忘记 Stop; 4. HTTP Body 忘记 Close。

它们共同点都是「某个东西一直在等」。写 goroutine 的时候顺手问一句:它什么时候退出?答不出来的,基本上就是漏了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。