wwqdrh

内存泄漏实例

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)
    }
}