go goroutine泄露

什么是goroutine泄露

Go 中的并发性是以 goroutine(独立活动)和 channel(用于通信)的形式实现的。处理 goroutine 时,程序员需要小心翼翼地避免泄露。如果最终永远堵塞在 I/O 上(例如 channel 通信),或者陷入死循环,那么 goroutine 会发生泄露。即使是阻塞的 goroutine,也会消耗资源,因此,程序可能会使用比实际需要更多的内存,或者最终耗尽内存,从而导致崩溃。让我们来看看几个可能会发生泄露的例子。然后,我们将重点关注如何检测程序是否受到这种问题的影响。

可能发生goroutine泄露的情况

1 发送到一个没有接收者的 channel
假设出于冗余的目的,程序发送请求到许多后端。使用首先收到的响应,丢弃后面的响应。下面的代码将会通过等待随机数毫秒,来模拟向下游服务器发送请求:

package main

import (  
    "fmt"  
    "math/rand"  
    "runtime"  
    "time"  
)

func query() int {  
    n := rand.Intn(100)  
    time.Sleep(time.Duration(n) * time.Millisecond)  
    return n  
}

func queryAll() int {  
    ch := make(chan int)  
    go func() { ch <- query() }()  
    go func() { ch <- query() }()  
    go func() { ch <- query() }()  
    return <-ch  
}

func main() {  
    for i := 0; i < 4; i++ {  
        queryAll()  
        fmt.Printf("#goroutines: %d", runtime.NumGoroutine())  
    }  
}

输出:

#goroutines: 3  
#goroutines: 5  
#goroutines: 7  
#goroutines: 9

每次调用 queryAll 后,goroutine 的数目会发生增长。问题在于,在接收到第一个响应后,“较慢的” goroutine 将会发送到另一端没有接收者的 channel 中。

可能的解决方法是,如果提前知道后端服务器的数量,那么使用缓存 channel。否则,只要至少有一个 goroutine 仍在工作,我们就可以使用另一个 goroutine 来接收来自这个 channel 的数据。其他的解决方案可能是使用 context(example),利用 某些机制来取消其他请求。

2 从没有发送者的 channel 中接收数据
这种场景类似于发送到一个没有接收者的 channel。泄露 goroutine 这篇文章中包含了一个示例。

nil channel
写入到 nil channel 会永远阻塞:

package main

func main() {  
    var ch chan struct{}  
    ch <- struct{}{}  
}

所以它导致死锁:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send (nil chan)]:  
main.main()  
...

当从 nil channel 读取数据时,同样的事情发生了:

var ch chan struct{}  
<-ch

当传递尚未初始化的 channel 时,也可能会发生:

package main

import (  
    "fmt"  
    "runtime"  
    "time"  
)

func main() {  
    var ch chan int  
    if false {  
        ch = make(chan int, 1)  
        ch <- 1  
    }  
    go func(ch chan int) {  
        <-ch  
    }(ch)

    c := time.Tick(1 * time.Second)  
    for range c {  
        fmt.Printf("#goroutines: %d", runtime.NumGoroutine())  
    }  
}

在这个例子中,有一个显而易见的罪魁祸首 —— if false {,但是在更大的程序中,更容易忘记这件事,然后使用 channel 的零值(nil)。

3 死循环
goroutine 泄露不仅仅是因为 channel 的错误使用造成的。泄露的原因也可能是 I/O 操作上的堵塞,例如发送请求到 API 服务器,而没有使用超时。另一种原因是,程序可以单纯地陷入死循环中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值