golang slice使用不慎导致的问题

原文链接 : http://www.bugclosed.com/post/16

背景

go语言中切片slice是方便且好用的强大数据结构,但是使用的时候需要注意,不然容易出问题,最近因为遇到了一个slice的使用问题,比较典型。
有一个功能需求,用户需要获取1-20的不重复随机序列。

逻辑实现

由于是需要固定的1-20共20个不同数字,所以直接定义好了唯一序列如下:

var(
	originalNumbers = []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,13,14,15,16, 17, 18, 19, 20}
)

因为每个用户获取的数据的序列都需要随机打乱,实现的逻辑如下:


func shuffle(list []uint32) []uint32 {
	n := len(list)

	for i := n - 1; i > 0; i-- {
		j := dist.Int63n(int64(i + 1))
		list[i], list[j] = list[j], list[i]
	}

	return list
}

func getOriginalNumbers() []uint32{
	return originalNumbers
}

func GetRandomNumbers(cardType int) []uint32 {
	return shuffle(getOriginalNumbers())
}

问题暴露

通过仔细分析,从以上的逻辑其实是可以发现问题的,只是写代码的时候疏忽导致没有主要到潜在问题。运行的时候发现逻辑不正确,偶尔有用户得到的序列是有重复的数字。

从原始数据的初始化来看,数字是1-20初始化到slice里面的,绝对不会出现重复。仔细看了GetRandoNumbers和shuffle打乱逻辑是存在并发访问问题的。

首先originalNumbers是一个slice,参数传递slice时仅仅是传递的切片的指针,并非复制一份切片。所以在并发的情况下,每个用户的GetRandomNumbers都会获取到同一个slice地址。而shuffle函数会对得到切片数据进行写操作(数据打乱),当出现并发写问题的时候,数据发生错乱就不足为奇了。

问题解决

这个问题本质就是并发写问题,只需要将数据分离即可解决问题。

func getOriginalNumbers() []uint32{
	tmp := make([]uint32, len(originalNumbers))
	copy(tmp, shortDeck)
	return tmp
}

总结

这是一个很典型的slice误用问题,slice是一个数据结构,他会指向底层真正的内存数据块,可以认为slice传递的是内存的指针。

posted @ 2018-05-24 16:00  乐观黑胡子  阅读(381)  评论(0编辑  收藏  举报