当你启动一个 goroutine 并且这个 goroutine 没有正常终止或者不能被垃圾回收,它将持续占用内存,导致内存泄漏。
发生内存泄漏的情况可以简单概况为: 主goroutine结束之后子goroutine还在运行。
goroutine内存泄漏实现
三种情况:
- 子goroutine死循环
- 使用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()
}