Go切片導(dǎo)致rand.Shuffle產(chǎn)生重復(fù)數(shù)據(jù)的原因與解決方案
問(wèn)題描述
在一個(gè) Go 服務(wù)端 API 里,我們需要按照 curBatch
參數(shù)進(jìn)行分頁(yè),從 interestCfg
里分批選取 interestTagNum
個(gè)興趣標(biāo)簽,并在返回結(jié)果前對(duì)選中的數(shù)據(jù)進(jìn)行隨機(jī)打亂。
全部興趣標(biāo)簽示例:
{ "InterestTags": [ {"interestName":"Daily Sharing"}, {"interestName":"Gaming"}, {"interestName":"AI"}, {"interestName":"test"}, {"interestName":"Sports"}, {"interestName":"Cars"}, {"interestName":"other"} ] }
現(xiàn)象回顧
當(dāng) curBatch = 0
時(shí),返回的數(shù)據(jù)是正確的:
{ "InterestTags": [ { "interestName": "Daily Sharing" }, { "interestName": "Gaming" }, { "interestName": "AI" } ] }
但當(dāng) curBatch = 2
時(shí),測(cè)試環(huán)境出現(xiàn)了數(shù)據(jù)重復(fù)的問(wèn)題:(本地運(yùn)行正常)
1. 不隨機(jī)時(shí)(正確的結(jié)果):
{ "InterestTags": [ { "interestName": "other" }, { "interestName": "Daily Sharing" }, { "interestName": "Gaming" } ] }
2. 隨機(jī)后(錯(cuò)誤的結(jié)果):
{ "InterestTags": [ { "interestName": "Gaming" }, { "interestName": "Gaming" }, { "interestName": "AI" } ] }
問(wèn)題:
- “Gaming” 出現(xiàn)了兩次,而 “test” 消失了!
- 本地環(huán)境正常,但測(cè)試環(huán)境異常,導(dǎo)致調(diào)試變得困難。
問(wèn)題排查
數(shù)據(jù)的選擇和隨機(jī)操作邏輯如下:
interestTags := make([]model.InterestConfig, 0, interestConfig.InterestTagNum) // 處理interestConfig,根據(jù)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] } } // 隨機(jī)打亂 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] })
關(guān)鍵點(diǎn)分析
interestTags = interestAllTags[startIdx:endIdx]
直接從interestAllTags
取出數(shù)據(jù),但切片是引用類(lèi)型,因此interestTags
共享了interestAllTags
的底層數(shù)組。rand.Shuffle
隨機(jī)交換interestTags
里的元素,但interestTags
指向interestAllTags
,可能導(dǎo)致原始數(shù)據(jù)被錯(cuò)誤修改。- 本地和測(cè)試環(huán)境不一致,可能與 Go 運(yùn)行時(shí)的內(nèi)存管理機(jī)制或高并發(fā)場(chǎng)景下的切片擴(kuò)容行為有關(guān)。
代碼驗(yàn)證
為了驗(yàn)證 interestTags
是否共享 interestAllTags
的底層數(shù)組,我們打印切片元素的內(nèi)存地址:
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 進(jìn)行數(shù)據(jù)拷貝
為了避免 interestTags
共享 interestAllTags
的底層數(shù)組,我們需要顯式拷貝數(shù)據(jù):
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]...) }
為什么這樣做?
append(..., interestAllTags[startIdx:endIdx]...)
創(chuàng)建新的切片,避免interestTags
共享interestAllTags
的底層數(shù)據(jù)。- 獨(dú)立的數(shù)據(jù)拷貝 確保
rand.Shuffle
只影響interestTags
,不會(huì)破壞原始interestAllTags
。
總結(jié)
1. 問(wèn)題原因
- Go 切片是引用類(lèi)型,直接賦值
interestTags = interestAllTags[startIdx:endIdx]
不會(huì)創(chuàng)建新數(shù)據(jù),而是共享底層數(shù)組。 rand.Shuffle
可能影響interestAllTags
,導(dǎo)致元素重復(fù)。- 本地環(huán)境正常,但測(cè)試環(huán)境異常,可能與 Go 內(nèi)存管理和切片擴(kuò)容策略有關(guān)。
2. 解決方案
- 使用
append
進(jìn)行數(shù)據(jù)拷貝,確保interestTags
是獨(dú)立的數(shù)據(jù),避免rand.Shuffle
影響原始interestAllTags
。
經(jīng)驗(yàn)總結(jié)
- Go 切片是引用類(lèi)型,不能直接賦值,否則可能共享底層數(shù)據(jù)。
- 使用
rand.Shuffle
之前,必須確保數(shù)據(jù)是獨(dú)立的副本。 - 盡量使用
append
創(chuàng)建新的切片,避免底層數(shù)組共享問(wèn)題。 - 不同環(huán)境表現(xiàn)不一致時(shí),應(yīng)檢查內(nèi)存管理、并發(fā)情況及數(shù)據(jù)結(jié)構(gòu)副作用。
以上就是Go切片導(dǎo)致rand.Shuffle產(chǎn)生重復(fù)數(shù)據(jù)的原因與解決方案的詳細(xì)內(nèi)容,更多關(guān)于Go rand.Shuffle產(chǎn)生重復(fù)數(shù)據(jù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang讀取http的body時(shí)遇到的坑及解決
這篇文章主要介紹了golang讀取http的body時(shí)遇到的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03golang http 連接超時(shí)和傳輸超時(shí)的例子
今天小編就為大家分享一篇golang http 連接超時(shí)和傳輸超時(shí)的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07詳解Go語(yǔ)言中用 os/exec 執(zhí)行命令的五種方法
這篇文章主要介紹了Go語(yǔ)言中用 os/exec 執(zhí)行命令的五種方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11Go通過(guò)goroutine實(shí)現(xiàn)多協(xié)程文件上傳的基本流程
多協(xié)程文件上傳是指利用多線(xiàn)程或多協(xié)程技術(shù),同時(shí)上傳一個(gè)或多個(gè)文件,以提高上傳效率和速度,本文給大家介紹了Go通過(guò)goroutine實(shí)現(xiàn)多協(xié)程文件上傳的基本流程,需要的朋友可以參考下2024-05-05以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作
這篇文章主要介紹了以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12