Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go rand.Shuffle产生重复数据

Go切片导致rand.Shuffle产生重复数据的原因与解决方案

作者:飞川001

在 Go 语言的实际开发中,切片(slice)是一种非常灵活的数据结构,然而,由于其底层数据共享的特性,在某些情况下可能会导致意想不到的 Bug,本文将详细分析 rand.Shuffle 之后,切片中的数据出现重复的问题,探讨其根本原因,并给出最佳解决方案,需要的朋友可以参考下

问题描述

在一个 Go 服务端 API 里,我们需要按照 curBatch 参数进行分页,从 interestCfg 里分批选取 interestTagNum 个兴趣标签,并在返回结果前对选中的数据进行随机打乱。

全部兴趣标签示例:

{
    "InterestTags": [
        {"interestName":"Daily Sharing"},
        {"interestName":"Gaming"},
        {"interestName":"AI"},
        {"interestName":"test"},
        {"interestName":"Sports"},
        {"interestName":"Cars"},
        {"interestName":"other"}
    ]
}

现象回顾

当 curBatch = 0 时,返回的数据是正确的:

{
    "InterestTags": [
        { "interestName": "Daily Sharing" },
        { "interestName": "Gaming" },
        { "interestName": "AI" }
    ]
}

但当 curBatch = 2 时,测试环境出现了数据重复的问题:(本地运行正常)

1. 不随机时(正确的结果):

{
    "InterestTags": [
        { "interestName": "other" },
        { "interestName": "Daily Sharing" },
        { "interestName": "Gaming" }
    ]
}

2. 随机后(错误的结果):

{
    "InterestTags": [
        { "interestName": "Gaming" },
        { "interestName": "Gaming" },
        { "interestName": "AI" }
    ]
}

问题:

问题排查

数据的选择和随机操作逻辑如下:

interestTags := make([]model.InterestConfig, 0, interestConfig.InterestTagNum)

// 处理interestConfig,根据curBatch分批次处理
if len(interestConfig.InterestCfg) > 0 && interestConfig.InterestTagNum > 0 {
    interestAllTags := interestConfig.InterestCfg
    numBatches := (len(interestAllTags) + int(interestConfig.InterestTagNum) - 1) / int(interestConfig.InterestTagNum)
    startIdx := (curBatch % numBatches) * int(interestConfig.InterestTagNum)
    endIdx := startIdx + int(interestConfig.InterestTagNum)

    if endIdx > len(interestAllTags) {
        interestTags = interestAllTags[startIdx:]
        interestTags = append(interestTags, interestAllTags[:(endIdx-len(interestAllTags))]...)
    } else {
        interestTags = interestAllTags[startIdx:endIdx]
    }
}

// 随机打乱 interestTags 顺序
r := rand.New(rand.NewSource(time.Now().UnixNano()))
r.Shuffle(len(interestTags), func(i, j int) {
    interestTags[i], interestTags[j] = interestTags[j], interestTags[i]
})

关键点分析

  1. interestTags = interestAllTags[startIdx:endIdx] 直接从 interestAllTags 取出数据,但切片是引用类型,因此 interestTags 共享了 interestAllTags 的底层数组
  2. rand.Shuffle 随机交换 interestTags 里的元素,但 interestTags 指向 interestAllTags,可能导致原始数据被错误修改
  3. 本地和测试环境不一致,可能与 Go 运行时的内存管理机制高并发场景下的切片扩容行为有关。

代码验证

为了验证 interestTags 是否共享 interestAllTags 的底层数组,我们打印切片元素的内存地址:

fmt.Println("Before Shuffle:")
for i, tag := range interestTags {
    fmt.Printf("[%d] %p: %s\n", i, &interestTags[i], tag.InterestName)
}

r.Shuffle(len(interestTags), func(i, j int) {
    interestTags[i], interestTags[j] = interestTags[j], interestTags[i]
})

fmt.Println("After Shuffle:")
for i, tag := range interestTags {
    fmt.Printf("[%d] %p: %s\n", i, &interestTags[i], tag.InterestName)
}

解决方案

方案 1:使用 append 进行数据拷贝

为了避免 interestTags 共享 interestAllTags 的底层数组,我们需要显式拷贝数据:

interestTags = make([]model.InterestConfig, 0, interestConfig.InterestTagNum)
if endIdx > len(interestAllTags) {
    interestTags = append(interestTags, interestAllTags[startIdx:]...)
    interestTags = append(interestTags, interestAllTags[:(endIdx-len(interestAllTags))]...)
} else {
    interestTags = append(interestTags, interestAllTags[startIdx:endIdx]...)
}

为什么这样做?

总结

1. 问题原因

2. 解决方案

经验总结

  1. Go 切片是引用类型,不能直接赋值,否则可能共享底层数据。
  2. 使用 rand.Shuffle 之前,必须确保数据是独立的副本
  3. 尽量使用 append 创建新的切片,避免底层数组共享问题。
  4. 不同环境表现不一致时,应检查内存管理、并发情况及数据结构副作用。

以上就是Go切片导致rand.Shuffle产生重复数据的原因与解决方案的详细内容,更多关于Go rand.Shuffle产生重复数据的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文