golang容易导致内存泄漏的6种情况汇总

 更新时间:2023年01月09日 09:44:01   作者:Yuan_sr  
内存泄漏是我们在生产环境中必须面临的问题,下面这篇文章主要给大家介绍了关于golang容易导致内存泄漏的6种情况,文中通过实例代码介绍的非常详细,需要的朋友可以参考下

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
}

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

总结

到此这篇关于golang容易导致内存泄漏的6种情况的文章就介绍到这了,更多相关golang内存泄漏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Gin+Gorm实现CRUD的实战

    Gin+Gorm实现CRUD的实战

    本文主要介绍了Gin+Gorm实现CRUD的实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • golang图片处理库image基本操作

    golang图片处理库image基本操作

    这篇文章主要介绍了golang图片处理库image简介,主要包括图片的基本读取与保存及图片的修改,本文通过通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • golang连接MongoDB数据库及数据库操作指南

    golang连接MongoDB数据库及数据库操作指南

    MongoDB是Nosql中常用的一种数据库,下面这篇文章主要给大家介绍了关于golang连接MongoDB数据库及数据库操作的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • GO语言基础入门第一个go程序解读

    GO语言基础入门第一个go程序解读

    这篇文章主要为大家介绍了GO语言基础入门的第一个go程序解读,下面来带大家进入Go语言世界helloworld的大门吧,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-11-11
  • Go基于GORM 获取当前请求所执行的 SQL 信息(思路详解)

    Go基于GORM 获取当前请求所执行的 SQL 信息(思路详解)

    这篇文章主要介绍了Go基于GORM 获取当前请求所执行的 SQL 信息(思路详解),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Go1.18新特性使用Generics泛型进行流式处理

    Go1.18新特性使用Generics泛型进行流式处理

    这篇文章主要为大家介绍了Go1.18新特性使用Generics泛型进行流式处理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Golang 手写一个简单的并发任务 manager

    Golang 手写一个简单的并发任务 manager

    这篇文章主要介绍了Golang 手写一个简单的并发任务 manager,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-08-08
  • Go语言defer的一些神奇规则示例详解

    Go语言defer的一些神奇规则示例详解

    这篇文章主要为大家介绍了Go语言defer的一些神奇规则示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Golang try catch与错误处理的实现

    Golang try catch与错误处理的实现

    社区不少人在谈论 golang 为毛不用try/catch模式,而采用苛刻的recovery、panic、defer组合,本文就来详细的介绍一下,感兴趣的可以了解一下
    2021-07-07
  • Go语言清除文件中空行的方法

    Go语言清除文件中空行的方法

    这篇文章主要介绍了Go语言清除文件中空行的方法,实例分析了Go语言针对文件的操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02

最新评论