当你启动一个 goroutine 并且这个 goroutine 没有正常终止或者不能被垃圾回收,它将持续占用内存,导致内存泄漏。

发生内存泄漏的情况可以简单概况为: 主goroutine结束之后子goroutine还在运行

goroutine内存泄漏实现

三种情况:

  1. 子goroutine死循环
  2. 使用channel阻塞
package main

import (
    "fmt"
    "net/http"
    _ "net/http/pprof"
    "time"
)

func doWork1() {
    for {
        // 这个工作永不结束
        fmt.Println("doing work")
        time.Sleep(1 * time.Second)
    }
}

func doWork2(c chan struct{}) {
    // 子gotoutine阻塞
    <-c
    fmt.Println("doing work")
    time.Sleep(1 * time.Second)
}

func doWork3(i *int) {
    // 存在闭包
    (*i)++
    fmt.Println(*i)
}

func main() {

    go func() {
        _ = http.ListenAndServe("localhost:6060", nil)
    }()
    go doWork1() // 子goroutine死循环
    // c := make(chan struct{}, 0)
    // go doWork2(c) // 使用channel阻塞
    // i := 1
    // go doWork3(&i) // 闭包

    // 主线程休眠,让 Goroutine 有机会运行
    time.Sleep(2 * time.Second)
}

/*
当前实现三种情况,不过只有doWork1和doWork2实现了内存泄漏。
*/

检查 是否存在goroutine发生内存泄漏

这里有多种方法:

  • 如果程序运行过程中内存一直增长,大概率出现内存泄漏。再去查看goroutine逻辑
  • 使用pprof检查goroutine的数量是否发生改变
package main

import (
    "fmt"
    "net/http"
    _ "net/http/pprof" // 引入pprof
    "time"
)

func doWork1() {
    for {
        // 这个工作永不结束
        fmt.Println("doing work")
        time.Sleep(1 * time.Second)
    }
}

func main() {
    // 启动pprof服务
    go func() {
        _ = http.ListenAndServe("localhost:6060", nil)
    }()
    go doWork1() // 子goroutine死循环
    time.Sleep(2 * time.Second)
}
  • 利用第三方库goleak
package main

import (
    "testing"

    "go.uber.org/goleak"
)

func TestSomething(t *testing.T) {
    defer goleak.VerifyNone(t) // 原理是在查看运行前后的goroutine是否一致,一致则通过,不一致则不通过

    main()
}