• 切片 零值 浅拷贝 泄露 扩容


    实践:

    1、

    	type T struct {
    		A string
    		B []string
    		C bool
    		D map[string]string
    		E map[string][]string
    	}
    
    	t1 := T{"a1",
    		[]string{"s1"},
    		true, map[string]string{"m1": "v1"},
    		map[string][]string{"m1": {"a", "b"}}}
    
    	t2 := t1
    	t2.A = "a2"
    	t2.B = []string{"s2"}
    	t2.C = false
    	t2.D = map[string]string{"m1": "v2"}
    	t2.E = map[string][]string{"m1": {"a", "b", "c"}}
    	t3 := t1
    	// t3.B[0] = "s3"
    	// t3.D["m1"] = "v3"
    	// t3.E["m1"][0] = "v3"
    	fmt.Println(t1, t2)
    	fmt.Println(t1, t2, t3)
    

      去掉注释后

     点评:

    1-1、拷贝的是指向对象的指针,同时新建切片、词典则开辟新的地址;修改时,前者改的是同一地址的值,后者是不同地址的值。

    2、

    	s := make([]int, 0, 10)
    	var f = func(s []int) {
    		s = append(s, 10, 20, 30)
    		fmt.Println(s)
    	}
    	fmt.Println(s)
    	f(s)
    	fmt.Println(s[:10])
    	
    	s1 := make([]int, 10)
    	fmt.Println(s1)
    	f(s1)
    	fmt.Println(s1[:10])
    
    	s2 := make([]int, 2, 10)
    	fmt.Println(s2)
    	f(s2)
    	fmt.Println(s2[:10])
    

      

     注意:

    		s := make([]int, 2, 10)
    		s[2] = 123
    

    runtime error: index out of range [2] with length 2

    		s := make([]int, 2, 10)
    		// s[2] = 123
    		fmt.Println(s[:10])
    		fmt.Println(s[2])
    	
    

      [0 0 0 0 0 0 0 0 0 0]

    runtime error: index out of range [2] with length 2

    https://mp.weixin.qq.com/s/i2jA817fXK7TxyA3lLP22A

    浅拷贝和泄露

    在写 Go 程序时,我们经常要用到 slice、map 等基础类型。但有一个比较麻烦的点,就是会涉及到浅拷贝。

    一个不注意就会引起 BUG,如下代码:

    type T struct {
     A string
     B []string
    }

    func main() {
     x := T{"煎鱼", []string{"上班"}}

     y := x
     y.A = "咸鱼"
     y.B[0] = "下班"

     fmt.Println(x)
     fmt.Println(y)
    }

    输出结果是什么?

    煎鱼到底是上班了,还是下班了?

    结果如下:

    {煎鱼 [下班]}
    {咸鱼 [下班]}

    实际上在 y := x 时,他拷贝的是指向对象的指针,这个时候 x 和 y 的底层数据其实是一家子,自然一变动 yx 的煎鱼也就下班了。

    同类型的 slice 也有 append 的泄露,以及 len、cap 的不准确问题,是比较折腾人的。

    泄露的示例:

    var a []int

    func f(b []int) []int {
     a = b[:2]
     return a
    }

    func main() {
        ...
    }

    有兴趣的可以具体看《Go 切片导致内存泄露,被坑两次了!》的解析。

    Go 切片导致内存泄露,被坑两次了! https://mp.weixin.qq.com/s/3Nsm_CFE7r60f6vFyyWkuw

    提出的是 Go 中很容易踩坑的切片内存泄露问题。作为宠粉的煎鱼肯定不会放过,争取让大家都避开这个 “坑”。

    今天这篇文章,就由煎鱼带大家来了解这个问题:Go 切片可能可以怎么泄露法?

    切片泄露的可能

    在业务代码的编写上,我们经常会接受来自外部的接口数据,再把他插入到对应的数据结构中去,再进行下一步的业务聚合、裁剪、封装、处理。

    像在 PHP 语言,常常会放到数组(array)中。在 Go 语言,会放到切片(slice)中。因此在 Go 的切片处理逻辑中,常常会涉及到如下类似的动作。

    示例代码如下:

    var a []int

    func f(b []int) []int {
     a = b[:2]
     return a
    }

    func main() {
        ...
    }

    仔细想想,这段程序有没有问题,是否存在内存泄露的风险?

    答案是:有的。有明确的切片内存泄露的可能性和风险。

    切片底层结构

    可能有些小伙伴会疑惑,怎么就有问题了,是哪里有问题?

    这里就得复习一下切片的底层基本数据结构了,切片在运行时的表现是 SliceHeader 结构体,定义如下:

    type SliceHeader struct {
     Data uintptr
     Len  int
     Cap  int
    }
    • Data:指向具体的底层数组。
    • Len:代表切片的长度。
    • Cap:代表切片的容量。

    要点是:切片真正存储数据的地方,是一个数组。切片的 Data 属性中存储的是指向所引用的数组指针地址

    背后的原因

    在上述案例中,我们有一个包全局变量 a,共有 2 个切片 a 和 b,截取了 b 的一部分赋值给了 a,两者存在着关联。

    从程序的直面来看,截取了 b 的一部分赋值给了 a,结构似乎是如下图:

    图片

    但我们进一步打开程序底层来看,他应该是如下图所示:

    图片

    切片 a 和 b 都共享着同一个底层数组(共享内存块),sliceB 包含全部所引用的字符。sliceA 只包含了 [:2],也就是 0 和 1 两个索引位的字符。

    那他们泄露在哪里了?

    泄露的点

    泄露的点,就在于虽然切片 b 已经在函数内结束了他的使命了,不再使用了。但切片 a 还在使用,切片 a 和 切片 b 引用的是同一块底层数组(共享内存块)。

    关键点:切片 a 引用了底层数组中的一段

    图片

    虽然切片 a 只有底层数组中 0 和 1 两个索引位正在被使用,其余未使用的底层数组空间毫无作用。但由于正在被引用,他们也不会被 GC,因此造成了泄露。

    解决办法

    解决的办法,就是利用切片的特性。当切片的容量空间不足时,会重新申请一个新的底层数组来存储,让两者彻底分手

    示例代码如下:

    var a []int
    var c []int    // 第三者

    func f(b []int) []int {
     a = b[:2]
      
      // 新的切片 append 导致切片扩容
     c = append(c, b[:2]...)
     fmt.Printf("a: %p\nc: %p\nb: %p\n", &a[0], &c[0], &b[0])
      
     return a
    }

    输出结果:

    a: 0xc000102060
    c: 0xc000124010
    b: 0xc000102060

    这段程序,新增了一个变量 c,他容量为 0。此时将期望的数据,追加过去。自然而然他就会遇到容量空间不足的情况,也就能实现申请新底层数据。

    我们再将原本的切片置为 nil,就能成功实现两者分手的目标了。

    总结

    在今天这篇文章中,我们介绍了 Go 切片的一种常见的内存泄露方式。虽然我们在日常使用的时候可能没关注到。

    主要原因还是由于切片的大多数使用场景,体量都比较小。又或是不知不觉就自己扩容了,就变成暂时性泄露了。

    这依然是存在风险的,在编写 Go 代码时需要谨慎。毕竟这可是 Go 语言官方自己都踩过坑的 “坑”

     

    Go 切片导致内存泄露,被坑两次了! https://eddycjy.com/posts/go/slice-leak/

     Go 切片这道题,吵了一个下午! https://mp.weixin.qq.com/s/kEQI74ge6VhvNEr1d3JW-Q

     

     

     
  • 相关阅读:
    IP应用加速技术详解:如何提升动静混合站点的访问速率?
    阿里云PolarDB发布重大更新 支持Oracle等数据库一键迁移上云
    BigData NoSQL —— ApsaraDB HBase数据存储与分析平台概览
    洛谷P1457 城堡 The Castle
    洛谷P1461 海明码 Hamming Codes
    洛谷P1460 健康的荷斯坦奶牛 Healthy Holsteins
    洛谷P1459 三值的排序 Sorting a Three-Valued Sequence
    洛谷P1458 顺序的分数 Ordered Fractions
    洛谷P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib
    洛谷P1215 [USACO1.4]母亲的牛奶 Mother's Milk
  • 原文地址:https://www.cnblogs.com/rsapaper/p/14575628.html
Copyright © 2020-2023  润新知