Go语言多核并行取决于GOMAXPROCS值,而非goroutine数量;它控制P的数量,决定真正并行的OS线程上限,且网络轮询器仍为单线程瓶颈。

Go语言在多核架构下能真正并行,但不是靠“写几个go就自动多核”——关键在于调度器是否被允许使用多个OS线程,而这个开关就是GOMAXPROCS。
为什么写了100个goroutine却只跑在一个CPU核心上
这是最常被误解的点。goroutine本身不等于并行;它只是并发单元。Go运行时默认把所有goroutine调度到一个OS线程(M)上执行,哪怕你机器有32核,runtime.NumCPU()返回32,只要GOMAXPROCS没设对,就还是单核忙死、其余空转。
- Go 1.5之前:默认
GOMAXPROCS=1,必须手动调用runtime.GOMAXPROCS(n) - Go 1.5+:默认值已设为
runtime.NumCPU(),但如果你在程序启动前显式调用了runtime.GOMAXPROCS(1),或环境变量GOMAXPROCS=1生效,那依然锁死单核 - 检查方式:运行时打印
runtime.GOMAXPROCS(0),看返回值是不是你预期的核心数
GOMAXPROCS设多少才合适
不能盲目设成CPU核心总数。它控制的是“可并行执行用户代码的OS线程上限”,不是“越多越好”。
- CPU密集型任务(如矩阵计算、哈希遍历):设为
runtime.NumCPU()通常合理 - 大量IO+少量CPU的任务(如HTTP服务):设太高反而增加调度开销,可能不如默认值稳定
- 混合型任务:建议从默认值开始压测,观察
pprof中syscall和GC占比,再微调 - 注意:
GOMAXPROCS只影响用户Go代码的并行度,不影响网络轮询器(netpoller)——后者仍是单线程,这是Go 1.22仍存在的设计约束
goroutine数量 ≠ 并行度,P的数量才是关键
Go调度器的GMP模型里,P(Processor)是调度上下文,每个P绑定一个OS线程(M),并维护自己的goroutine运行队列。真正决定并行能力的是P的数量,也就是GOMAXPROCS的值。
立即学习“go语言免费学习笔记(深入)”;
- 即使你启动10万goroutine,如果
GOMAXPROCS=1,它们全挤在一个P里轮转,本质是协作式并发,非并行 - 当
GOMAXPROCS=8,最多8个P同时工作,每个P可独立在不同CPU核心上执行goroutine - 但P不是越多越好:P过多会导致缓存行失效(false sharing)、调度器争抢全局队列,实测中超过物理核心数2倍后性能常下降
网络服务多核利用率低?不是goroutine的问题
HTTP服务启动几十个goroutine处理请求,但top里始终只有一个CPU核心100%,这不是GOMAXPROCS没设对,而是Go网络栈的单轮询器(netpoller)瓶颈。
- 所有socket就绪事件都由同一个OS线程上的
epoll_wait(Linux)捕获,这个线程固定绑定在第一个P上 - 业务逻辑(如JSON解析)可以并行,但“收包”这一步永远卡在单核
- 解决方法不是加goroutine,而是进程级扩展:用
SO_REUSEPORT启动多个server进程,让内核分发连接 -
runtime.LockOSThread()对netpoller无效——你锁不住轮询器线程,它不归你管
真正容易被忽略的,是区分“并发”和“并行”:goroutine解决的是并发组织问题,GOMAXPROCS和P的数量才决定是否发生并行;而网络I/O的并行瓶颈,得绕过Go运行时,交给操作系统层面解决。


















