开发者

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

目录
  • 问题描述
  • 问题排查
  • 代码验证
  • 解决方案
    • 方案 1:使用 append 进行数据拷贝
    • 为什么这样做?
  • 总结
    • 1. 问题原因
    • 2. 解决方案
  • 经验总结

    问题描述

    在一个 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" }
        ]
    }
    

    问题:

    • “Gaming” 出现了两次,而 “test” 消失了!
    • 本地环境正常,但测试环境异常,导致调试变得困难。

    问题排查

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

    interestTags := make([]model.InterestConfig, 0, interestConfig.InterestTagNum)
    
    // 处理interestConfig,根据curBatch分批次处理
    if len(inwww.devze.comterestConfig.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) {js
        interestTags[i], interestTags[j] = interestTags[j], interestTags[i]
    })
    

    关键点分析

    1. interestTags = interestAllTags[startIdx:endIdx] 直接从 interestAllTags 取出数据,但切片是引用类型,因此 interestTags 共享了 interestAllTags 的底层数组
    2. rand.Shuffle 随机交换 interestTagspython 里的元素,但 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 {
        interestTagspython = append(interestTags, interestAllTags[startIdx:endIdx]...)
    }
    

    为什么这样做?

    • append(..., interestAllTags[startIdx:endIdx]...) 创建新的切片,避免 interestTags 共享 interestAllTags 的底层数据。
    • 独立的数据拷贝 确保 rand.Shuffle 只影响 interestTags,不会破坏原始 interestAllTags

    总结

    1. 问题原因

    • Go 切片是引用类型,直接赋值 interestTags = interestAllTags[startIdx:endIdx] 不会创建新数据,而是共享底层数组
    • rand.Shuffle 可能影响 interestAllTags,导致元素重复
    • 本地环境正常,但测试环境异常,可能与 Go 内存管理切片扩容策略有关。

    2. 解决方案

    • 使用 append 进行数据拷贝,确保 interestTags 是独立的数据,避免 rand.Shuffle 影响原始 interestAllTags

    经验总结

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

    以上就是Go切片导致rand.Shuffle产生重复数据的原因与解决方案的详细内容,更多关于Go rand.Shuffle产生重复数据的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜