站长资讯网
最全最丰富的资讯网站

golang内存泄漏原因有哪些

泄漏原因有:1、time.After()的使用,每次time.After(duration x)会产生NewTimer(),在duration x到期之前,新创建的timer不会被GC,到期之后才会GC;2、time.NewTicker资源未及时释放;3、select阻塞;4、channel阻塞;5、申请过多的goroutine、goroutine阻塞;6、slice引起的等。

golang内存泄漏原因有哪些

本教程操作环境:windows7系统、GO 1.18版本、Dell G3电脑。

golang容易导致内存泄漏的几种情况

1. 定时器使用不当

1.1 time.After()的使用

默认的time.After()是会有内存泄露问题的,因为每次time.After(duration x)会产生NewTimer(),在duration x到期之前,新创建的timer不会被GC,到期之后才会GC。

随着时间推移,尤其是duration x很大的话,会产生内存泄露的问题,应特别注意

for true { 	select { 	case <-time.After(time.Minute * 3):     // do something   default: 		time.Sleep(time.Duration(1) * time.Second) 	} }
登录后复制

为了保险起见,使用NewTimer()或者NewTicker()代替的方式主动释放资源,两者的区别请自行查阅或看我往期文章https://blog.csdn.net/weixin_38299404/article/details/119352884

timer := time.NewTicker(time.Duration(2) * time.Second) defer timer.Stop() for true { 	select { 	case <-timer.C: 		// do something 	default: 		time.Sleep(time.Duration(1) * time.Second) 	} }
登录后复制

1.2 time.NewTicker资源未及时释放

在使用time.NewTicker时需要手动调用Stop()方法释放资源,否则将会造成永久性的内存泄漏

timer := time.NewTicker(time.Duration(2) * time.Second) // defer timer.Stop() for true { 	select { 	case <-timer.C: 		// do something 	default: 		time.Sleep(time.Duration(1) * time.Second) 	} }
登录后复制

2. select阻塞

使用select时如果有case没有覆盖完全的情况且没有default分支进行处理,最终会导致内存泄漏

2.1 导致goroutine阻塞的情况

func main() {     ch1 := make(chan int)     ch2 := make(chan int)     ch3 := make(chan int)     go Getdata("https://www.baidu.com",ch1)     go Getdata("https://www.baidu.com",ch2)     go Getdata("https://www.baidu.com",ch3)     select{         case v:=<- ch1:             fmt.Println(v)         case v:=<- ch2:             fmt.Println(v)     } }
登录后复制

上述这种情况会阻塞在ch3的消费处导致内存泄漏

2.2 循环空转导致CPU暴涨

func main() { 	fmt.Println("main start") 	msgList := make(chan int, 100) 	go func() { 		for { 			select { 			case <-msgList: 			default:   			} 		} 	}() 	 	c := make(chan os.Signal, 1) 	signal.Notify(c, os.Interrupt, os.Kill) 	s := <-c 	 	fmt.Println("main exit.get signal:", s) }
登录后复制

上述for循环条件一旦命中default则会出现循环空转的情况,并最终导致CPU暴涨

3. channel阻塞

channel阻塞主要分为写阻塞和读阻塞两种情况

空channel

func channelTest() {   	//声明未初始化的channel读写都会阻塞     var c chan int   	//向channel中写数据     go func() {         c <- 1         fmt.Println("g1 send succeed")         time.Sleep(1 * time.Second)     }()   	//从channel中读数据     go func() {         <-c         fmt.Println("g2 receive succeed")         time.Sleep(1 * time.Second)     }()     time.Sleep(10 * time.Second) }
登录后复制

写阻塞

  • 无缓冲channel的阻塞通常是写操作因为没有读而阻塞

func channelTest() {     var c = make(chan int)   	//10个协程向channel中写数据     for i := 0; i < 10; i++ {         go func() {             <- c             fmt.Println("g1 receive succeed")             time.Sleep(1 * time.Second)         }()     }   	//1个协程丛channel读数据     go func() {         c <- 1         fmt.Println("g2 send succeed")         time.Sleep(1 * time.Second)     }()   	//会有写的9个协程阻塞得不到释放     time.Sleep(10 * time.Second) }
登录后复制

  • 有缓冲的channel因为缓冲区满了,写操作阻塞

func channelTest() {     var c = make(chan int, 8)   	//10个协程向channel中写数据     for i := 0; i < 10; i++ {         go func() {             <- c             fmt.Println("g1 receive succeed")             time.Sleep(1 * time.Second)         }()     }   	//1个协程丛channel读数据     go func() {         c <- 1         fmt.Println("g2 send succeed")         time.Sleep(1 * time.Second)     }()   	//会有写的几个协程阻塞写不进去     time.Sleep(10 * time.Second) }
登录后复制

读阻塞

  • 期待从channel读数据,结果没有goroutine往进写数据

func channelTest() {    var c = make(chan int)   //1个协程向channel中写数据   go func() {     <- c     fmt.Println("g1 receive succeed")     time.Sleep(1 * time.Second)   }()   //10个协程丛channel读数据   for i := 0; i < 10; i++ {     go func() {         c <- 1         fmt.Println("g2 send succeed")         time.Sleep(1 * time.Second)     }()   }   //会有读的9个协程阻塞得不到释放   time.Sleep(10 * time.Second) }
登录后复制

4. goroutine导致的内存泄漏

4.1 申请过多的goroutine

例如在for循环中申请过多的goroutine来不及释放导致内存泄漏

4.2 goroutine阻塞

4.2.1 I/O问题

I/O连接未设置超时时间,导致goroutine一直在等待,代码会一直阻塞。

4.2.2 互斥锁未释放

goroutine无法获取到锁资源,导致goroutine阻塞

//协程拿到锁未释放,其他协程获取锁会阻塞 func mutexTest() {     mutex := sync.Mutex{}     for i := 0; i < 10; i++ {         go func() {             mutex.Lock()             fmt.Printf("%d goroutine get mutex", i)       			//模拟实际开发中的操作耗时             time.Sleep(100 * time.Millisecond)         }()     }     time.Sleep(10 * time.Second) }
登录后复制

4.2.3 死锁

当程序死锁时其他goroutine也会阻塞

func mutexTest() {     m1, m2 := sync.Mutex{}, sync.RWMutex{}   	//g1得到锁1去获取锁2     go func() {         m1.Lock()         fmt.Println("g1 get m1")         time.Sleep(1 * time.Second)         m2.Lock()         fmt.Println("g1 get m2")     }()     //g2得到锁2去获取锁1     go func() {         m2.Lock()         fmt.Println("g2 get m2")         time.Sleep(1 * time.Second)         m1.Lock()         fmt.Println("g2 get m1")     }()   	//其余协程获取锁都会失败     go func() {         m1.Lock()         fmt.Println("g3 get m1")     }()     time.Sleep(10 * time.Second) }
登录后复制

4.2.4 waitgroup使用不当

waitgroup的Add、Done和wait数量不匹配会导致wait一直在等待

5. slice 引起的内存泄漏

当两个slice 共享地址,其中一个为全局变量,另一个也无法被GC;

append slice 后一直使用,没有进行清理。

var a []int   func test(b []int) {         a = b[:3]         return }
登录后复制

6. 数组的值传递

由于数组时Golang的基本数据类型,每个数组占用不通的内存空间,生命周期互不干扰,很难出现内存泄漏的情况,但是数组作为形参传输时,遵循的时值拷贝,如果函数被多个goroutine调用且数组过大时,则会导致内存使用激增。

//统计nums中target出现的次数 func countTarget(nums [1000000]int, target int) int {     num := 0     for i := 0; i < len(nums) && nums[i] == target; i++ {         num++     }     return num }
登录后复制

因此对于大数组放在形参场景下通常使用切片或者指针进行传递,避免短时间的内存使用激增、

赞(0)
分享到: 更多 (0)
网站地图   沪ICP备18035694号-2    沪公网安备31011702889846号