【摘要】 以下是我学习中所接触的关于Goroutine内存泄漏的例子
首先内存泄漏的情况会有如下几种:
Goroutine 内正在进行 channel/mutex 等读写操作,但由于逻辑问题,某些情况下会被一直阻塞。Goroutine 内的业务逻辑进入死循环,资源一直无法释放。Goroutine 内的业务逻辑进入长时间等待,有不断新增的 Goroutine 进入等待。
de…
以下是我学习中所接触的关于Goroutine内存泄漏的例子
首先内存泄漏的情况会有如下几种:
- Goroutine 内正在进行 channel/mutex 等读写操作,但由于逻辑问题,某些情况下会被一直阻塞。
- Goroutine 内的业务逻辑进入死循环,资源一直无法释放。
- Goroutine 内的业务逻辑进入长时间等待,有不断新增的 Goroutine 进入等待。
demo1:
func main() {
for i := 0; i < 4; i++ {
queryAll()
fmt.Printf("goroutines: %d\n", runtime.NumGoroutine())
}
}
func queryAll() int {
ch := make(chan int)
for i := 0; i < 3; i++ {
go func() { ch <- query() }()
} //开启了三个协程,有两个协程堵塞
return <-ch
}
func query() int {
n := rand.Intn(100)
time.Sleep(time.Duration(n) * time.Millisecond)
return n
}
上面的输出结果是:
goroutines: 3
goroutines: 5
goroutines: 7
goroutines: 10
可看到输出的 goroutines 数量是在不断增加的,每次多 2 个。也就是每调用一次queryAll(),都会泄露 Goroutine。原因在于 无缓冲channel 均已经发送了(每次发送 3 个),但是在接收端并没有完全接收(只接收 1 次 ch),所诱发的 Goroutine 泄露。并且main函数本身也算是一个goroutine
demo2:
func main() {
defer func() {
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
var ch chan struct{} //必须make
go func() {
ch <- struct{}{} //因为非缓冲?必须得有协程接收它才能存放?
}()
//<-ch
time.Sleep(time.Second)
}
输出结果很明显是:goroutines: 2
抛开管道chan没有事先make在进行使用不说,对于非缓冲管道,必须有接收者才能将数据放入管道,否则就会阻塞!从而导致内存泄漏。
demo3:
func main() {
defer func() {
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
var ch chan int
go func() {
<-ch
}()
time.Sleep(time.Second)
}
与demo2相同道理,无缓冲管道不能只有一方在操作,否则就会堵塞
demo4:
//奇怪的慢等待
func main() {
httpClient := http.Client{
//Timeout: time.Second * 2, //设置httpClicent的超时时间,否则由于响应慢而一直堵塞
}
for {
go func() { resp, err := httpClient.Get("https://www.github.com/") if err != nil { fmt.Printf("http.Get err: %v\n", err) } else { fmt.Printf(resp.Status) }
}()
time.Sleep(time.Second * 1)
fmt.Println("goroutines: ", runtime.NumGoroutine())
}
}
输出结果:(由于在我这经常访问不了github,所以用这个做测试~)
goroutines: 5
200 OK
200 OK
goroutines: 3
goroutines: 4
在上面demo中,是一个 Go 语言中经典的事故场景。也就是一般我们会在应用程序中去调用第三方服务的接口。但是第三方接口,有时候会很慢,久久不返回响应结果。刚好在Go 语言中默认的 http.Client 是没有设置超时时间的。因此就会导致一直阻塞,Goroutine数量 自然也就持续暴涨,不断泄露,最终占满资源,导致事故。
若我们设置了超时时间,若响应很慢也能结束掉这个goroutine,不至于导致内存泄漏!
demo5:
//互斥锁忘记解锁
func main() {
total := 0
defer func() {
time.Sleep(time.Second)
fmt.Println("total: ", total)
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
var mutex sync.Mutex
for i := 0; i < 10; i++ {
go func() { mutex.Lock() total += 1
}()
}
}
上面的代码输出结果如下:
total: 1
goroutines: 10
在上面这个demo中,第一个互斥锁 sync.Mutex 加锁了,由于他可能在处理业务逻辑,或者是忘记 Unlock 解锁了。因此导致后面的所有 sync.Mutex 想加锁,却因锁未释放又都阻塞住了。 建议在加锁后来一句defer mutex.Unlock()
last demo:
//同步锁使用不当
func handle(v int) {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < v; i++ {
fmt.Println("巴拉巴拉巴拉...")
wg.Done()
}
wg.Wait()
}
func main() {
defer func() {
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
go handle(3)
time.Sleep(time.Second)
}
输出结果:
巴拉巴拉巴拉…
巴拉巴拉巴拉…
巴拉巴拉巴拉…
goroutines: 2
由于使用了同步锁进行编排!但是由于wg.Add的数量与wg.Done数量不匹配,导致wg.Wait一直在等待而导致阻塞!所以可以输出了3个巴拉是传进去参数的结果,但是该goroutine并没有结束,一直在等待还差两个wg.Done没有执行,所以程序结束前有两个goroutine。所以怎么处理这种情况呢,建议在for循环内执行wg.Add(1),defer wg.Done()以及dosomething…
以上就是今天的六个小demo!要坚持每天学习喔!
文章来源: blog.csdn.net,作者:F-Coding,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/S_FMX/article/details/116172834