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

