go 的 goroutine 本身不等于并行计算,需显式设置 GOMAXPROCS 为 CPU 核数并避免阻塞才能实现真正并行;CPU 密集任务应分片、限 worker 数、用 channel 同步而非盲目启大量 goroutine。

Go 的 goroutine 本身不等于“并行计算”,它只是轻量级并发执行单元;真正实现 CPU 密集型并行计算,必须配合多核调度和避免阻塞——否则你写的全是并发,不是并行。
goroutine 默认不自动利用多核
Go 运行时默认只使用一个 OS 线程(GOMAXPROCS=1),所有 goroutine 都在这个线程上协作式调度,无法并行执行 CPU 密集任务。
实操建议:
- 启动前显式设置:
runtime.GOMAXPROCS(runtime.NumCPU()),推荐在main()开头调用 - 不要依赖环境变量
GOMAXPROCS—— Go 1.5+ 虽默认设为 CPU 核数,但某些容器或嵌入场景仍可能为 1 - 可通过
runtime.GOMAXPROCS(0)读取当前值,用于调试或日志
CPU 密集型任务必须避免 runtime.Park / syscall 阻塞
一旦 goroutine 进入系统调用(如文件读写、网络等待)或主动调用 runtime.Gosched(),它会让出 P,但不会触发其他 M 启动;而纯计算无阻塞时,P 会一直被占用,其他 goroutine 只能等当前 P 空闲。
立即学习“go语言免费学习笔记(深入)”;
常见错误现象:
- 启了 1000 个
goroutine做矩阵加法,但 top 显示 CPU 占用始终只有 100%(单核) -
pprof显示大量时间花在runtime.mcall或runtime.gopark,说明调度器频繁干预
正确做法:
- 对纯计算任务,用
for range分片 + 固定数量goroutine(比如runtime.NumCPU()个),每个处理一批数据 - 避免在循环中调用
time.Sleep、fmt.Println等非必要系统交互 - 若需中间结果同步,优先用
channel配合select,而非互斥锁 + 条件等待
sync.WaitGroup + channel 是最可控的并行模式
直接用 go f() 启一堆 goroutine 容易失控:内存暴涨、调度延迟、结果收集困难。生产环境应结构化管理生命周期。
典型结构示例(分片求和):
func parallelSum(data []int, workers int) int { chunkSize := (len(data) + workers - 1) / workers ch := make(chan int, workers) var wg sync.WaitGroup <pre class="brush:php;toolbar:false;">for i := 0; i < workers; i++ { wg.Add(1) go func(start int) { defer wg.Done() end := min(start+chunkSize, len(data)) sum := 0 for j := start; j < end; j++ { sum += data[j] } ch <- sum }(i * chunkSize) } go func() { wg.Wait() close(ch) }() total := 0 for s := range ch { total += s } return total
}
关键点:
- 限制
workers数量,通常设为runtime.NumCPU(),而非 len(data) -
ch缓冲大小设为workers,避免 sender 阻塞在 send 上(否则会卡住整个流程) - 用
go func() { wg.Wait(); close(ch) }()确保 channel 关闭时机可控
真正难的不是启动 goroutine,而是让它们不互相干扰、不抢锁、不制造 GC 压力、不因小失误退化成串行——这些细节比语法更决定并行效果。