内存泄漏实例
1、
package main
import (
"fmt"
"io/ioutil"
"net/http"
"runtime"
)
func main() {
num := 6
for index := 0; index < num; index++ {
resp, _ := http.Get("https://www.baidu.com")
_, _ = ioutil.ReadAll(resp.Body)
}
fmt.Printf("此时goroutine个数= %d\n", runtime.NumGoroutine())
}
注意,如果从单元测试中进行测试,那么就是4个,因为单元测试函数入口也会算一个😹
- 虽然执行了 6 次循环,而且每次都没有执行 Body.Close() ,就是因为执行了ioutil.ReadAll()把内容都读出来了,连接得以复用,因此只泄漏了一个读goroutine和一个写goroutine,最后加上main goroutine,所以答案就是3个goroutine。
- 从另外一个角度说,正常情况下我们的代码都会执行 ioutil.ReadAll(),但如果此时忘了 resp.Body.Close(),确实会导致泄漏。但如果你调用的域名一直是同一个的话,那么只会泄漏一个 读goroutine 和一个写goroutine,这就是为什么代码明明不规范但却看不到明显内存泄漏的原因。
2、
func fn() {
pub := func(start int) {
for j := start; j < count; j += concurrency {
select {
case subscribers[j] <- msg:
case <-time.After(time.Millisecond * 5):
case <-b.exit:
return
}
}
}
for i := 0; i < concurrency; i++ {
go pub(i)
}
}
被遗弃的time.After定时任务还是在时间堆里面,定时任务未到期之前,是不会被gc清理的,所以这就是会造成内存泄漏的原因
time.After
虽然调用的是timer
定时器,但是他没有使用time.Reset()
方法再次激活定时器,所以每一次都是新创建的实例,才会造成的内存泄漏,我们添加上time.Reset
每次重新激活定时器,即可完成解决问题。
func fn2(msg interface{}, subscribers []chan interface{}) {
count := 100
concurrency := 1
//采用Timer 而不是使用time.After 原因:time.After会产生内存泄漏 在计时器触发之前,垃圾回收器不会回收Timer
pub := func(start int) {
idleDuration := 5 * time.Millisecond
idleTimeout := time.NewTimer(idleDuration)
defer idleTimeout.Stop()
for j := start; j < count; j += concurrency {
// 如果明确time已经expired,并且t.C已经读取过值,那么可以直接使用Reset;如果程序之前没有从t.C中读取过值,这时需要首先调用Stop(),如果返回true,说明timer还没有expire,stop成功删除timer,可直接reset;如果返回false,说明stop前已经expire,需要显式drain channel。
if !idleTimeout.Stop() {
select {
case <-idleTimeout.C:
// 避免hang住 有可能已经被读取过了
default:
}
}
// 它的返回值不代表重设定时器成功或失败,而是在表达定时器在重设前的状态:- 当Timer已经停止或者超时,返回false。, - 当定时器未超时时,返回true。
idleTimeout.Reset(idleDuration)
select {
case subscribers[j] <- msg:
case <-idleTimeout.C:
}
}
}
for i := 0; i < concurrency; i++ {
go pub(i)
}
}