網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
背景
在實(shí)現(xiàn)圖片轉(zhuǎn)碼的需求時(shí),需要支持最大 500 個(gè)圖片下載后轉(zhuǎn)換格式;
如果是一個(gè)一個(gè)下載后轉(zhuǎn)碼,耗時(shí)太長(zhǎng),需要使用 goroutine 實(shí)現(xiàn) 500 個(gè)圖片并發(fā)下載后,并發(fā)轉(zhuǎn)碼;
但自測(cè)過(guò)程中發(fā)現(xiàn),會(huì)偶現(xiàn)下載后只轉(zhuǎn)換了 499 個(gè)圖片或更少的情況(全部下載、轉(zhuǎn)碼成功的條件下);
然后就開(kāi)始了打印日志找 bug 的過(guò)程。
排查問(wèn)題
因?yàn)椴l(fā)時(shí)使用到了 sync 等待全部協(xié)程結(jié)束,起初以為是 sync 異步等待出了問(wèn)題;
打印日志發(fā)現(xiàn),正常執(zhí)行了 500 次下載,執(zhí)行完成下載之后,繼續(xù)執(zhí)行的轉(zhuǎn)碼操作,排除 sync 異步等待有問(wèn)題;
代碼如下:
import ( "github.com/satori/go.uuid" "sync" ) func downloadFiles(nWait *sync.WaitGroup, urls []interface{}, successFiles *[]string, failedFiles *[]string) { // 遍歷 urls 進(jìn)行下載 for _, value := range urls { go func(value interface{}) { defer nWait.Done() // 執(zhí)行結(jié)束,協(xié)程減 1 fullname := config.TranscodeDownloadPath + "/" + uuid.NewV4().String() // 需要確保文件名的唯一性 (防止不同用戶同一時(shí)間操作了同一文件,導(dǎo)致轉(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 進(jìn)行下載 log.Error("開(kāi)始下載!長(zhǎng)度:", len(strUrlList)) nWait.Add(len(strUrlList)) // 等待協(xié)程數(shù) downloadFiles(&nWait, strUrlList, &successFiles, &failedFiles) nWait.Wait() // 阻塞,等待完成 log.Error("下載結(jié)束!長(zhǎng)度:", len(successFiles)) //... log.Error("下載轉(zhuǎn)碼!") //...
日志如下:
2022-10-29 21:28:51.996 ERROR ? services/tools.go:149 ? 開(kāi)始下載!長(zhǎng)度:500
2022-10-29 21:28:52.486 ERROR ? services/tools.go:153 ? 下載結(jié)束!長(zhǎng)度:499
2022-10-29 21:28:52.486 ERROR ? services/tools.go:155 ? 開(kāi)始轉(zhuǎn)碼!
打印更詳細(xì)的日志,對(duì) for range 循環(huán)內(nèi)的邏輯進(jìn)行排查;
在單個(gè) for 循環(huán)結(jié)束時(shí)增加日志:
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
兩次長(zhǎng)度都是 65,切片長(zhǎng)度沒(méi)有發(fā)生變化,同一時(shí)間點(diǎn)執(zhí)行兩次切片 append 方法,會(huì)偶現(xiàn)一次失效,問(wèn)題原因找到;
解決問(wèn)題
使用切片索引進(jìn)行賦值,不再使用 append ;
修復(fù)代碼如下:
import ( "github.com/satori/go.uuid" "sync" ) func downloadFiles(nWait *sync.WaitGroup, urls []interface{}, successFiles *[]string, failedFiles *[]string) { // 遍歷 urls 進(jìn)行下載 for index, value := range urls { go func(index int, value interface{}) { defer nWait.Done() // 執(zhí)行結(jié)束,協(xié)程減 1 fullname := config.TranscodeDownloadPath + "/" + uuid.NewV4().String() // 需要確保文件名的唯一性 (防止不同用戶同一時(shí)間操作了同一文件,導(dǎo)致轉(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 進(jìn)行下載 nWait.Add(len(strUrlList)) // 等待協(xié)程數(shù) downloadFiles(&nWait, strUrlList, &successFiles, &failedFiles) nWait.Wait() // 阻塞,等待完成
原文鏈接:https://juejin.cn/post/7159967055217688590
相關(guān)推薦
- 2021-12-08 c語(yǔ)言單詞本的新增、刪除、查詢按順序顯示功能_C 語(yǔ)言
- 2023-01-31 基于C#實(shí)現(xiàn)FTP下載文件_C#教程
- 2022-04-19 前端如何使用webpack優(yōu)化性能
- 2022-11-14 swiftui開(kāi)發(fā)之padding默認(rèn)值設(shè)置詳解_Swift
- 2022-09-05 Redis 數(shù)據(jù)刪除策略
- 2022-07-01 Oracle中的索引講解_oracle
- 2022-05-25 Flutter實(shí)現(xiàn)掃二維碼功能_Android
- 2022-11-09 go+redis實(shí)現(xiàn)消息隊(duì)列發(fā)布與訂閱的詳細(xì)過(guò)程_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支