You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 02:45:32