go高并發(fā)時append方法偶現(xiàn)錯誤解決分析
背景
在實現(xiàn)圖片轉(zhuǎn)碼的需求時,需要支持最大 500 個圖片下載后轉(zhuǎn)換格式;
如果是一個一個下載后轉(zhuǎn)碼,耗時太長,需要使用 goroutine 實現(xiàn) 500 個圖片并發(fā)下載后,并發(fā)轉(zhuǎn)碼;
但自測過程中發(fā)現(xiàn),會偶現(xiàn)下載后只轉(zhuǎn)換了 499 個圖片或更少的情況(全部下載、轉(zhuǎn)碼成功的條件下);
然后就開始了打印日志找 bug 的過程。
排查問題
因為并發(fā)時使用到了 sync 等待全部協(xié)程結(jié)束,起初以為是 sync 異步等待出了問題;
打印日志發(fā)現(xiàn),正常執(zhí)行了 500 次下載,執(zhí)行完成下載之后,繼續(xù)執(zhí)行的轉(zhuǎn)碼操作,排除 sync 異步等待有問題;
代碼如下:
import ( "github.com/satori/go.uuid" "sync" ) func downloadFiles(nWait *sync.WaitGroup, urls []interface{}, successFiles *[]string, failedFiles *[]string) { // 遍歷 urls 進行下載 for _, value := range urls { go func(value interface{}) { defer nWait.Done() // 執(zhí)行結(jié)束,協(xié)程減 1 fullname := config.TranscodeDownloadPath + "/" + uuid.NewV4().String() // 需要確保文件名的唯一性 (防止不同用戶同一時間操作了同一文件,導致轉(zhuǎn)碼失敗) err := utils.DownloadCeph(value.(string), fullname) // 下載文件 // 下載文件狀態(tài)記錄 if err != nil { *failedFiles = append(*failedFiles, fullname) } else { *successFiles = append(*successFiles, fullname) } }(value) } } // 前端傳入的圖片 url strUrlList := req["strUrlList"] // 初始化變量 nWait := sync.WaitGroup{} // 多協(xié)程異步等待 var successFiles []string // 下載成功文件 var failedFiles []string // 下載失敗文件 // 遍歷 strUrlList 進行下載 log.Error("開始下載!長度:", len(strUrlList)) nWait.Add(len(strUrlList)) // 等待協(xié)程數(shù) downloadFiles(&nWait, strUrlList, &successFiles, &failedFiles) nWait.Wait() // 阻塞,等待完成 log.Error("下載結(jié)束!長度:", len(successFiles)) //... log.Error("下載轉(zhuǎn)碼!") //...
日志如下:
2022-10-29 21:28:51.996 ERROR services/tools.go:149 開始下載!長度:500
2022-10-29 21:28:52.486 ERROR services/tools.go:153 下載結(jié)束!長度:499
2022-10-29 21:28:52.486 ERROR services/tools.go:155 開始轉(zhuǎn)碼!
打印更詳細的日志,對 for range 循環(huán)內(nèi)的邏輯進行排查;
在單個 for 循環(huán)結(jié)束時增加日志:
log.Error("下載協(xié)程結(jié)束: ", len(*successFiles))
發(fā)現(xiàn)一處特殊的日志:
2022-10-29 21:40:38.407 ERROR services/tools.go:35 下載協(xié)程結(jié)束: 63
2022-10-29 21:40:38.407 ERROR services/tools.go:35 下載協(xié)程結(jié)束: 64
2022-10-29 21:40:38.407 ERROR services/tools.go:35 下載協(xié)程結(jié)束: 65
2022-10-29 21:40:38.407 ERROR services/tools.go:35 下載協(xié)程結(jié)束: 65
2022-10-29 21:40:38.408 ERROR services/tools.go:35 下載協(xié)程結(jié)束: 66
2022-10-29 21:40:38.408 ERROR services/tools.go:35 下載協(xié)程結(jié)束: 67
兩次長度都是 65,切片長度沒有發(fā)生變化,同一時間點執(zhí)行兩次切片 append 方法,會偶現(xiàn)一次失效,問題原因找到;
解決問題
使用切片索引進行賦值,不再使用 append ;
修復代碼如下:
import ( "github.com/satori/go.uuid" "sync" ) func downloadFiles(nWait *sync.WaitGroup, urls []interface{}, successFiles *[]string, failedFiles *[]string) { // 遍歷 urls 進行下載 for index, value := range urls { go func(index int, value interface{}) { defer nWait.Done() // 執(zhí)行結(jié)束,協(xié)程減 1 fullname := config.TranscodeDownloadPath + "/" + uuid.NewV4().String() // 需要確保文件名的唯一性 (防止不同用戶同一時間操作了同一文件,導致轉(zhuǎn)碼失敗) err := utils.DownloadCeph(value.(string), fullname) // 下載文件 // 下載文件狀態(tài)記錄 if err != nil { (*failedFiles)[index] = fullname } else { (*successFiles)[index] = fullname } }(index, value) } } // 前端傳入的圖片 url strUrlList := req["strUrlList"] // 初始化變量 nWait := sync.WaitGroup{} // 多協(xié)程異步等待 successFiles := make([]string, len(strUrlList), len(strUrlList)) // 下載成功文件 failedFiles := make([]string, len(strUrlList), len(strUrlList)) // 下載失敗文件 // 遍歷 strUrlList 進行下載 nWait.Add(len(strUrlList)) // 等待協(xié)程數(shù) downloadFiles(&nWait, strUrlList, &successFiles, &failedFiles) nWait.Wait() // 阻塞,等待完成
以上就是go高并發(fā)時append方法偶現(xiàn)錯誤解決分析的詳細內(nèi)容,更多關于go高并發(fā)append錯誤的資料請關注腳本之家其它相關文章!
相關文章
Golang微服務框架Kratos實現(xiàn)Kafka消息隊列的方法
消息隊列是大型分布式系統(tǒng)不可缺少的中間件,也是高并發(fā)系統(tǒng)的基石中間件,所以掌握好消息隊列MQ就變得極其重要,在本文當中,您將了解到:什么是消息隊列?什么是Kafka?怎樣在微服務框架Kratos當中應用Kafka進行業(yè)務開發(fā),需要的朋友可以參考下2023-09-09Golang中fsnotify包監(jiān)聽文件變化的原理詳解
Golang提供了一個強大的fsnotify包,它能夠幫助我們輕松實現(xiàn)文件系統(tǒng)的監(jiān)控,本文將深入探討fsnotify包的原理,感興趣的小伙伴可以跟隨小編一起學習一下2023-12-12利用go-kit組件進行服務注冊與發(fā)現(xiàn)和健康檢查的操作
這篇文章主要介紹了利用go-kit組件進行服務注冊與發(fā)現(xiàn)和健康檢查的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04