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ù),但切片是引用類型,因此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 切片是引用類型,直接賦值
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 切片是引用類型,不能直接賦值,否則可能共享底層數(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)文章
Go語(yǔ)言對(duì)接微信支付與退款指南(示例詳解)
在互聯(lián)網(wǎng)技術(shù)日益發(fā)展的背景下,Go語(yǔ)言憑借并發(fā)處理能力,在后端開(kāi)發(fā)中大放異彩,本文詳細(xì)介紹如何使用Go語(yǔ)言對(duì)接微信支付,完成支付和退款功能,包括準(zhǔn)備工作、初始化微信支付客戶端、實(shí)現(xiàn)支付功能,以及處理支付回調(diào)和退款等2024-10-10
Golang中的select語(yǔ)句及其應(yīng)用實(shí)例
本文將介紹Golang中的select語(yǔ)句的使用方法和作用,并通過(guò)代碼示例展示其在并發(fā)編程中的實(shí)際應(yīng)用,此外,還提供了一些與select相關(guān)的面試題,幫助讀者更好地理解和應(yīng)用select語(yǔ)句2023-12-12
Golang?rabbitMQ生產(chǎn)者消費(fèi)者實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Golang?rabbitMQ生產(chǎn)者消費(fèi)者實(shí)現(xiàn)的示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
基于golang中container/list包的用法說(shuō)明
這篇文章主要介紹了基于golang中container/list包的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
Go語(yǔ)言bufio庫(kù)的全面指南與實(shí)戰(zhàn)技巧詳解
這篇文章主要為大家全面介紹一下?bufio?庫(kù)的核心組件與功能,包括?Reader、Writer?和?Scanner?等并深入探討它們?cè)趯?shí)際編程中的運(yùn)用場(chǎng)景和技巧,感興趣的可以了解下2024-01-01
從errors到pkg-errors詳解Go語(yǔ)言中的錯(cuò)誤處理指南
錯(cuò)誤處理是編程語(yǔ)言中的重要組成部分,它直接影響程序的可靠性和可維護(hù)性,本文將深入探討Go語(yǔ)言中的錯(cuò)誤處理,從標(biāo)準(zhǔn)庫(kù)的errors包到第三方的pkg-errors庫(kù),幫助開(kāi)發(fā)者掌握錯(cuò)誤處理的最佳實(shí)踐,提高代碼的質(zhì)量和可維護(hù)性2026-04-04

