加入收藏 | 设为首页 | 会员中心 | 我要投稿 聊城站长网 (https://www.0635zz.com/)- 智能语音交互、行业智能、AI应用、云计算、5G!
当前位置: 首页 > 综合聚焦 > 编程要点 > 语言 > 正文

golang内存泄漏的因素是什么

发布时间:2023-07-08 12:53:41 所属栏目:语言 来源:
导读:这篇“golang内存泄漏的原因是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这
这篇“golang内存泄漏的原因是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“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容易导致内存泄漏的几种情况
 
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()代替的方式主动释放资源
 
 
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() {
 
    ch2 := make(chan int)
 
    ch3 := make(chan int)
 
    ch4 := make(chan int)
 
    go Getdata("https://www.baidu.com",ch2)
 
    go Getdata("https://www.baidu.com",ch3)
 
    go Getdata("https://www.baidu.com",ch4)
 
    select{
 
        case v:=<- ch2:
 
            fmt.Println(v)
 
        case v:=<- ch3:
 
            fmt.Println(v)
 
    }
 
}
 
上述这种情况会阻塞在ch4的消费处导致内存泄漏
 
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
 
}
 
因此对于大数组放在形参场景下通常使用切片或者指针进行传递,避免短时间的内存使用激增。
 
 

(编辑:聊城站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!