Go程序增加goroutine数量后性能下降问题排查求助
问题描述
我正在完成并行课程的小型项目,尝试过缓冲通道、非缓冲通道、基于切片指针的无通道实现等方案,也做了尽可能的优化,但遇到同一个问题:增加goroutine数量(哪怕只加1个)都会让程序整体运行变慢。想知道问题出在哪,这个场景还能不能通过并行化提升性能?
我的预期是:增加goroutine数量会让程序更快,直到达到某个数量后,再增加goroutine只会保持执行时间不变或略有变慢。
核心代码
main函数
func main() { rand.Seed(time.Now().UnixMicro()) numAgents := 2 fmt.Println("Please pick a number of goroutines: ") fmt.Scanf("%d", &numAgents) numFiles := 4 fmt.Println("How many files do you want?") fmt.Scanf("%d", &numFiles) start := time.Now() numAssist := numFiles channel := make(chan []File, numAgents) files := make([]File, 0) for i := 0; i < numAgents; i++ { if i == numAgents-1 { go generateFiles(numAssist, channel) } else { go generateFiles(numFiles/numAgents, channel) numAssist -= numFiles / numAgents } } for i := 0; i < numAgents; i++ { files = append(files, <-channel...) } elapsed := time.Since(start) fmt.Printf("Function took %s\n", elapsed) }
generateFiles函数
func generateFiles(numFiles int, channel chan []File) { magicNumbersMap := getMap() files := make([]File, 0) for i := 0; i < numFiles; i++ { content := randElementFromMap(&magicNumbersMap) length := rand.Intn(400) + 100 hexSlice := getHex() for j := 0; j < length; j++ { content = content + hexSlice[rand.Intn(len(hexSlice))] } hash := getSHA1Hash([]byte(content)) file := File{ content: content, hash: hash, } files = append(files, file) } channel <- files }
完整依赖代码
import ( "crypto/sha1" "encoding/base64" "fmt" "math/rand" "time" ) type File struct { content string hash string } func getMap() map[string]string { return map[string]string{ "D4C3B2A1": "Libcap file format", "EDABEEDB": "RedHat Package Manager (RPM) package", "4C5A4950": "lzip compressed file", } } func getHex() []string { return []string{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", } } func randElementFromMap(m *map[string]string) string { x := rand.Intn(len(*m)) for k := range *m { if x == 0 { return k } x-- } return "Error" } func getSHA1Hash(content []byte) string { h := sha1.New() h.Write(content) return base64.URLEncoding.EncodeToString(h.Sum(nil)) }
问题分析与解决方案
核心问题:全局rand锁竞争
程序变慢的根本原因是**rand包的全局随机数生成器带有互斥锁**。多个goroutine同时调用rand.Intn()时,会频繁竞争同一个锁,导致goroutine阻塞等待,抵消了并行的优势,甚至比单goroutine执行更慢。
针对性修复与优化
1. 给每个goroutine分配独立的rand生成器
避免全局锁竞争,每个goroutine使用专属的随机数源:
import "strings" func generateFiles(numFiles int, channel chan []File) { // 每个goroutine初始化独立的rand生成器 r := rand.New(rand.NewSource(time.Now().UnixMicro() + int64(rand.Intn(100000)))) magicNumbersMap := getMap() files := make([]File, 0, numFiles) // 预分配切片容量 hexSlice := getHex() // 提前获取,避免循环内重复创建 for i := 0; i < numFiles; i++ { // 使用局部r生成随机数,避开全局锁 x := r.Intn(len(magicNumbersMap)) var content string for k := range magicNumbersMap { if x == 0 { content = k break } x-- } length := r.Intn(400) + 100 // 用strings.Builder拼接字符串,减少内存拷贝 var buf strings.Builder buf.Grow(len(content) + length) buf.WriteString(content) for j := 0; j < length; j++ { buf.WriteString(hexSlice[r.Intn(len(hexSlice))]) } content = buf.String() hash := getSHA1Hash([]byte(content)) files = append(files, File{ content: content, hash: hash, }) } channel <- files }
2. 优化任务分配逻辑
原代码的任务拆分可能导致最后一个goroutine负载过重,改为均匀分配:
// 替换main函数中的goroutine启动循环 for i := 0; i < numAgents; i++ { start := i * numFiles / numAgents end := (i + 1) * numFiles / numAgents go generateFiles(end - start, channel) }
这样每个goroutine的任务量差异不超过1,负载更均衡。
3. 额外性能优化
- 复用SHA1哈希对象:给每个goroutine创建一个
sha1.Hash实例,循环内调用Reset()复用,减少对象创建开销 - 预分配所有需要的内存,避免运行时频繁扩容
效果验证
修改后,goroutine数量增加到CPU核心数左右时,程序执行时间会明显下降;超过核心数后,性能会趋于平稳或略有下降,符合你的预期。
内容的提问来源于stack exchange,提问作者HWDiv
相关产品推荐
相关产品推荐

