• 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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    为了保险起见,使用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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上述这种情况会阻塞在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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上述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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 写阻塞

      • 无缓冲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)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 有缓冲的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)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 读阻塞

      • 期待从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)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      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)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      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)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      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
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      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
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

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

  • 相关阅读:
    jquery插件--浮动广告
    【算法】【floodfill】洪水灌溉
    【玩转STL】STL的简介和string类用法和接口讲解(源码解析)
    数据分析9
    听说你要删库跑路了?这篇Linux脚本请收好
    TensorRT安装记录(8.2.5)
    [最新榜单] 智能手机数据恢复的 10 款最佳应用
    倾斜摄影技术构建图扑 WebGIS 智慧展馆
    leetcode:476. 数字的补数
    第二章 16位微处理器
  • 原文地址:https://blog.csdn.net/weixin_38299404/article/details/126805554