前言

先下结论:slice复用得当心,引用不当深埋雷。如若复用请分叉,分叉之后再使用。

问题

先看一下代码吧

 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
26
27
28
29
package main

import (
	"fmt"
)

func a() {
	x := []int{}
	x = append(x, 0)
	x = append(x, 1)  // commonTags := labelsToTags(app.Labels)
	y := append(x, 2) // Tags: append(commonTags, labelsToTags(d.Labels)...)
	z := append(x, 3) // Tags: append(commonTags, labelsToTags(d.Labels)...)
	fmt.Println(y, z)
}

func b() {
	x := []int{}
	x = append(x, 0)
	x = append(x, 1)
	x = append(x, 2)  // commonTags := labelsToTags(app.Labels)
	y := append(x, 3) // Tags: append(commonTags, labelsToTags(d.Labels)...)
	z := append(x, 4) // Tags: append(commonTags, labelsToTags(d.Labels)...)
	fmt.Println(y, z)
}

func main() {
	a()
	b()
}

上面的如此简单的代码,分析代码希望得到预期结果如下:

1
2
[0 1 2] [0 1 3]
[0 1 2 3] [0 1 2 4]

但是执行后,得到结果如下:

1
2
3
Michaels-iMac:golab eric$ go run slice.go  
[0 1 2] [0 1 3]
[0 1 2 4] [0 1 2 4]

原因分析

我们先不急着分析具体原因,我们对比一下下面这段代码,看看执行结果是怎么样

 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
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
	"fmt"
)

func a() {
	x := []int{}
	x = append(x, 0)
	x = append(x, 1)  // commonTags := labelsToTags(app.Labels)
	y := append(x, 2) // Tags: append(commonTags, labelsToTags(d.Labels)...)
	z := append(x, 3) // Tags: append(commonTags, labelsToTags(d.Labels)...)
	fmt.Println(y, z)
}

func deepcopy(src []int) []int {
	dst := make([]int, len(src))
	copy(dst, src)
	return dst
}

func b() {
	x := []int{}
	x = append(x, 0)
	x = append(x, 1)
	x = append(x, 2) // commonTags := labelsToTags(app.Labels)
	y := deepcopy(x)
	y = append(y, 3)  // Tags: append(commonTags, labelsToTags(d.Labels)...)
	z := append(x, 4) // Tags: append(commonTags, labelsToTags(d.Labels)...)
	fmt.Println(y, z)
}

func main() {
	a()
	b()
}

具体执行结果如下:

1
2
3
Michaels-iMac:golab eric$ go run slice-2.go    
[0 1 2] [0 1 3]
[0 1 2 3] [0 1 2 4]

对比错误的代码,会发现问题原因出现下面的代码:

1
2
	y := append(x, 3) // Tags: append(commonTags, labelsToTags(d.Labels)...)
	z := append(x, 4) // Tags: append(commonTags, labelsToTags(d.Labels)...)

从内存角度来思考,y与z对应是同一段内存,z的操作override的y的操作。(备注:y与z为什么会更对应同一段内存,请了解一下slice的实现及slice的巧妙的使用,也请阅读下面参考文章Golang slices gotcha

解决方法就是如果slice要进行复用的时候,进行深度copy再进行使用。

总结

slice在golang编程属于超高频使用,如果出现上面的错误,前期没有发现,如果上线,并且系统复杂,出现了问题,定位成本是很大。

虽然上面没有slice的内存分析,但是对于程序员来说学习一些内存知识还很帮助,理解上面的问题就是小case了。

参考

  1. Golang slices gotcha