• 关于go中资源泄漏/goroutine泄漏/内存泄漏/CPU打满等情况分析



    干货满满,还不收藏等啥呢?!

    目录

    资源泄漏

    典型案例1

    典型案例2

    goroutine泄漏

    典型案例1

    典型案例2

    典型情况3

    内存泄漏

    CPU打满


    资源泄漏

    时常发生在资源的操作中,如文件打开未关闭、流式请求不关闭。

    典型案例1

    循环内部每次需要执行一次defer,你怎么办?

    首先来看看一般做法:

    1.     for i := 1; i <= 5; {
    2.         defer fmt.Println(i) // Possible resource leak, 'defer' is called in a 'for' loop
    3.         i++
    4.     }


    首先,这里已经有了造成资源泄漏的可能性,如果说循环内部做一些资源操作,如文件操作等,
    如果是文件关闭,如果循环次数较大,相当于你循环结束时才会执行这n次close操作。

    但是这样真的可以吗,有的小伙伴就说了,很显然每次执行的时候i都不是真实的每次应该执行的值
    那给defer里传一下参数不就对了?

    1.     for i := 1; i <= 5; i++{
    2.         defer func(num int) {  // Possible resource leak, 'defer' is called in a 'for' loop
    3.             fmt.Println(i)
    4.         }(i)
    5.     }


    这样真的可以吗?
    显然没有达到我们的循环内部每次需要执行一次defer的需求,并且还有资源泄漏的嫌疑。
    那到底怎么办?

    1. func main() {
    2.     for i := 1; i <= 5; {
    3.         func(){
    4.             defer fmt.Println(i)   // 每次循环都会执行一次defer,不会押到循环结束后,避免资源泄漏的情况
    5.             i++
    6.         }()
    7.     }
    8. }

    典型案例2


    有close操作,换句话说有结束/关闭资源的操作,但并没有机会执行

    如:

    1.     fmt.Println("任务进行中")
    2.     a := 1
    3.     if a > 0 { // 模拟出现错误
    4.         fmt.Println("任务出现错误")
    5.         return
    6.     }
    7.     fmt.Println("任务正常结束")
    8.     defer func() {
    9.         fmt.Println("这里进行关闭资源等操作")  // 假设这里进行关闭资源等操作
    10.     }()

    此时你以为的你以为,关闭资源操作真的会执行吗,不会的,此时就会造成资源泄漏。

    goroutine泄漏

    顾名思义,由goroutine导致的泄漏,指(单/多个)goroutine长期驻留在内存中,野蛮的吞噬CPU和内存

    典型案例1


    goroutine的职能实际上已经结束了,但goroutine又迟迟不能退出、或goroutine的数量疯狂的非正常上涨。

    不能退出的原因比如引用它的chan一直不close(实际上已完成使命)这就造成了goroutine泄漏;
    非正常上涨原因就是对代码未能进行有效控制,没有对goroutine的数量做控制、没有对产生它的调用方做条件限制,虽然它很轻量,但也经不住无限制的上涨。

    代码如下:

    1.     fmt.Println("任务进行中")
    2.     ch := make(chan int)
    3.     // 这里模拟一个功能:发送三次数据后发送结束
    4.     go func() {
    5.         for i := 1; i <= 3; i++ {
    6.             ch <- i
    7.             fmt.Println("已发送: ", i)
    8.         }
    9.         fmt.Println("发送已结束了")
    10.     }()
    11.     go func() {  // 设为协程A
    12.         for i := range ch { // 这里不断读取ch的数据
    13.             fmt.Println("已接收: ", i)
    14.         }
    15.         fmt.Println("这里会很寂寞,因为永不会执行")
    16.     }()
    17.     select {}


    speed running:

    1. 任务进行中
    2. 已发送:  1
    3. 已接收:  1
    4. 已接收:  2
    5. 已发送:  2
    6. 已发送:  3
    7. 发送已结束了
    8. 已接收:  3
    9. fatal error: all goroutines are asleep - deadlock!


    显然死锁了,协程A的职能是接收来自ch的数据,接收完它就结束了,实际上已经接收完3就可以结束,但是它结束不了,因为它不知道对方是否发送完毕、自己要接收到什么时候才是个头?
    因此协程A就发生了泄漏。


    这个代码也不是很典型,因为死锁了它就结束了,更典型的一个情况如下。


    典型案例2


    goroutine内部有死循环一直在跑,因为代码bug导致一些条件产生了死循环,这时候该goroutine就永不退出,资源一直无法释放,导致泄漏。

    典型情况3

    数量暴增的情况。
    如你要处理数亿条数据(很大),遍历的、并发的开协程去跑,但偏偏每个数据的处理时长很久,这就有可能导致段时间内产生极多的goroutine,一下子就会挤爆内存
    导致资源占用非常大,也就是波峰持续时间较长,导致泄漏。

    避免这种情况就要求能较好的把控代码,关键时刻能解除阻塞、信号的及时通知、及时进行介入控制、避免无用的一直执行、白跑等。


    内存泄漏


    内存泄漏指内存野蛮增长且无法达到稳定状态,严重影响系统性能。

    CPU打满


    典型代码

    1.     fmt.Println("任务进行中")
    2.     ch := make(chan int)
    3.     go func() {
    4.         flag := false
    5.         if flag {
    6.             ch <- 0 // 永不会写
    7.         }
    8.     }()
    9.     // 这里模拟一个功能:发送三次数据后发送结束
    10.     go func() {
    11.         for {
    12.             select {
    13.             case <-ch:
    14.             default:
    15.                 fmt.Println("default")
    16.             }
    17.         }
    18.         fmt.Println("我什么时候能结束?")
    19.     }()
    20.     select {}

    模拟其它case没有机会执行,导致default快速不停空转,CPU一下子就打满了

  • 相关阅读:
    案例丨如何提升可视化分析能力?听听这两家企业怎么说
    二叉树非递归遍历
    LeetCode中等题之查找和替换模式
    视频监控系统/视频汇聚平台EasyCVR如何反向代理进行后端保活?
    自己动手从零写桌面操作系统GrapeOS系列教程——12.QEMU+GDB调试
    MATLAB中alignsignals函数使用
    Day774.能向 Redis 学到什么 -Redis 核心技术与实战
    jQuery对于链和捕获的实战研究
    淘宝官方开放平台API接口获取淘宝商品详情信息(价格、销量、优惠价等)
    设计模式之解释器模式
  • 原文地址:https://blog.csdn.net/HYZX_9987/article/details/125450605