最近在开发中使用了for range来遍历一个slice,结果在测试的时候遇到了bug,最后定位是错误使用for range造成的,这里记录一下:
func redisSlaveScanBigKeys(slaveClient *redis.Client, bigKeyChan chan *bigKeyInstance, wg *sync.WaitGroup) {
var cursor uint64 = 0
for {
page, cursor_new, error := slaveClient.Scan(cursor, "*", 10000).Result()
if error != nil {
fmt.Printf("scan keys error, client is %v, error is %s
", slaveClient, error.Error())
}
cursor = cursor_new
pipeCount := 0
piper := slaveClient.Pipeline()
pageCount := len(page)
keySlice := make([]*string, 0, 10)
for _, key := range page {
pageCount--
pipeCount++
piper.Type(key)
fmt.Println(key, ":", &key)
keySlice = append(keySlice, &key)
........
}
if cursor_new == 0 {
wg.Done()
break
}
}
}
上面的代码在测试运行中发现,每一个keyslice中的内容都相同,后来在红色的地方发现每一次for .. range .. 循环中的key值都不同,但是 &key 地址是同一个地址。于是,就想了解一下 for .. range .. 的实现:
对slice for range的实现 // The loop we generate: // for_temp := range // len_temp := len(for_temp) // for index_temp = 0; index_temp < len_temp; index_temp++ { // value_temp = for_temp[index_temp] // index = index_temp // value = value_temp // original body // } 对 map for range的实现 // The loop we generate: // var hiter map_iteration_struct // for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) { // index_temp = *hiter.key // value_temp = *hiter.val // index = index_temp // value = value_temp // original body // }
可以看到,对于slice 而言,for idx, value := range slice{} 中value的值是对slice中对应元素的拷贝。针对map而言,for key, value := range map 中 key, value的值也是对对应元素拷贝,如果使用该值变量的地址作为指向每个元素的指针,就会导致错误,在迭代时,返回的变量是一个迭代过程中根据切片依次赋值的新变量,所以值的地址总是相同的,导致结果不如预期。