首頁 > 軟體

go高並行時append方法偶現錯誤解決分析

2022-11-01 14:03:08

背景

在實現圖片轉碼的需求時,需要支援最大 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方法偶現錯誤解決分析的詳細內容,更多關於go高並行append錯誤的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com