go语言中的slice底层封装了数组,提供了方便的列表操作方法,是在go里面很常用的结构,但是它也有坑,踩上了,会把你折腾得怀疑人生的。不幸的是,我就被它折腾了一天,过程很让人崩溃,故记录一下,给自己提个醒,也希望能对大家有所启发。废话不多说,还是先上代码吧。
package main
import (
"log"
"sync"
"sync/atomic"
)
func main() {
row:=make([]interface{},10)//用一个slice缓存数据
c:=make(chan []interface{},10)//数据传输channel
go func() {//放数据线程
for i:=0;i<10;i++{
row1:=row[0:2]//取前两个元素作为一个新的slice
row1[0]=i//假设往新slice里面放入数据
c<-row1//把数据丢给channel
//time.Sleep(time.Millisecond)
}
c<-[]interface{}{-1}//给个结束标志
}()
wg:=sync.WaitGroup{}
wg.Add(1)
count:=int32(0)
go func() {//接收数据线程
for row:=range c{
if row[0]==-1{
break
}
log.Printf("%v",row[0])
//time.Sleep(time.Millisecond)
atomic.AddInt32(&count,1)
}
wg.Done()
}()
wg.Wait()
close(c)//关闭channel
log.Printf("count->%v",count)
}
这个程序,我们设想的输出结果应该是:
2021/06/02 21:02:40 0
2021/06/02 21:02:40 1
2021/06/02 21:02:40 2
2021/06/02 21:02:40 3
2021/06/02 21:02:40 4
2021/06/02 21:02:40 5
2021/06/02 21:02:40 6
2021/06/02 21:02:40 7
2021/06/02 21:02:40 8
2021/06/02 21:02:40 9
2021/06/02 21:02:40 count->10
然而,实际输出的结果却是:
2021/06/02 21:02:40 9
2021/06/02 21:02:40 9
2021/06/02 21:02:40 9
2021/06/02 21:02:40 9
2021/06/02 21:02:40 9
2021/06/02 21:02:40 9
2021/06/02 21:02:40 9
2021/06/02 21:02:40 9
2021/06/02 21:02:40 9
2021/06/02 21:02:40 9
2021/06/02 21:02:40 count->10
为啥呢,我们从0到9,一个一个数往channel里面放的,取出来也应该是0到9有序的一个数列啊,为什么全部变成9了呢?
那么究竟为什么呢,我们来仔细看看这行代码:
row1:=row[0:2]//取前两个元素作为一个新的slice
这里,我们新创建了一个slice命名为row1,我们以为它从row里面拿出前两个元素,然后新创建了一块内存区域,然后把这两个元素放到了新的内存区域;但这只是我们以为,实际上slice的底层根本就没有创建新的内存区域,它只是把row前两个元素的内存地址给了row1而已,所以channel里面的所有row1所指向的内存区域都是row前两个元素所在的区域,也正因为如此,当放数据线程最后将第一个元素的内容改成9以后,虽然接收线程接收到不同的row1,但得到的数据内容就都是9了。当然,如果接收线程接收数据比较快的话,能在放数据线程修改数据之前取到数据,就能得到正确结果。比如这样:
让放数据线程跑慢一点,当然这不能解决根本问题,要完全解决问题,还是得用make创建新的slice,就像这样:
总结:
在go里面,slice,map,interface,channel这四个结构都是引用类型,在实际使用中一定要注意数据的拷贝问题