本文由golang教學專欄跟大家介紹關於go高並發的時候,append方法偶現錯誤的情況,下面給大家詳細講解辦法,希望對需要的朋友有所幫助!
背景
在實現圖片轉碼的需求時,需要支援最大500 個圖片下載後轉換格式;
如果是一個一個下載後轉碼,耗時太長,需要使用goroutine 實現500 個圖片並發下載後,並發轉碼;
但自測過程中發現,會偶現下載後只轉換了499 個圖片或更少的情況(全部下載、轉碼成功的條件下);
然後就開始了列印日誌找bug 的過程。
排查問題
因為並發時使用到了sync 等待全部協程結束,起初以為是sync 異步等待出了問題;
列印日誌發現,正常執行了500 次下載,執行完成下載之後,繼續執行的轉碼操作,排除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() // 执行结束,协程减 1 fullname := config.TranscodeDownloadPath + "/" + uuid.NewV4().String() // 需要确保文件名的唯一性 (防止不同用户同一时间操作了同一文件,导致转码失败) err := utils.DownloadCeph(value.(string), fullname) // 下载文件 // 下载文件状态记录 if err != nil { *failedFiles = append(*failedFiles, fullname) } else { *successFiles = append(*successFiles, fullname) } }(value) } } // 前端传入的图片 url strUrlList := req["strUrlList"] // 初始化变量 nWait := sync.WaitGroup{} // 多协程异步等待 var successFiles []string // 下载成功文件 var failedFiles []string // 下载失败文件 // 遍历 strUrlList 进行下载 log.Error("开始下载!长度:", len(strUrlList)) nWait.Add(len(strUrlList)) // 等待协程数 downloadFiles(&nWait, strUrlList, &successFiles, &failedFiles) nWait.Wait() // 阻塞,等待完成 log.Error("下载结束!长度:", len(successFiles)) //... log.Error("下载转码!") //...
日誌如下:
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 下载结束!长度:499 2022-10-29 21:28:52.486 ERROR services/tools.go:155 开始转码!
列印更詳細的日誌,對for range 迴圈內的邏輯進行排查;
在單一for 迴圈結束時增加日誌:
log.Error("下载协程结束: ", len(*successFiles))
發現一處特殊的日誌:
2022-10-29 21:40:38.407 ERROR services/tools.go:35 下载协程结束: 63 2022-10-29 21:40:38.407 ERROR services/tools.go:35 下载协程结束: 64 2022-10-29 21:40:38.407 ERROR services/tools.go:35 下载协程结束: 65 2022-10-29 21:40:38.407 ERROR services/tools.go:35 下载协程结束: 65 2022-10-29 21:40:38.408 ERROR services/tools.go:35 下载协程结束: 66 2022-10-29 21:40:38.408 ERROR services/tools.go:35 下载协程结束: 67
兩次長度都是65,切片長度沒有變化,同一時間點執行兩次切片append 方法,會偶現一次失效,問題原因找到;
解決問題
使用切片索引進行賦值,不再使用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() // 执行结束,协程减 1 fullname := config.TranscodeDownloadPath + "/" + uuid.NewV4().String() // 需要确保文件名的唯一性 (防止不同用户同一时间操作了同一文件,导致转码失败) err := utils.DownloadCeph(value.(string), fullname) // 下载文件 // 下载文件状态记录 if err != nil { (*failedFiles)[index] = fullname } else { (*successFiles)[index] = fullname } }(index, value) } } // 前端传入的图片 url strUrlList := req["strUrlList"] // 初始化变量 nWait := sync.WaitGroup{} // 多协程异步等待 successFiles := make([]string, len(strUrlList), len(strUrlList)) // 下载成功文件 failedFiles := make([]string, len(strUrlList), len(strUrlList)) // 下载失败文件 // 遍历 strUrlList 进行下载 nWait.Add(len(strUrlList)) // 等待协程数 downloadFiles(&nWait, strUrlList, &successFiles, &failedFiles) nWait.Wait() // 阻塞,等待完成
以上是詳解go高併發時append出錯問題!的詳細內容。更多資訊請關注PHP中文網其他相關文章!