Go程序默认能用多核,但未跑满主因是代码未触发并行:同步I/O、锁竞争、串行计算等导致goroutine无法有效分发到多P;GOMAXPROCS默认已设为CPU核数,通常无需手动调整。

Go 程序默认就能用多核,但为什么没跑满?
Go 运行时默认启用 GOMAXPROCS 为机器逻辑 CPU 数(从 Go 1.5 起),所以不是“不能用多核”,而是你写的代码可能根本没触发并行执行。常见原因是:所有 goroutine 都在做同步 I/O、锁竞争严重、或大量串行计算压在一个 goroutine 里。检查 runtime.GOMAXPROCS(0) 的返回值,确认它确实等于你的 CPU 核心数;如果不是,才需要显式设置。
runtime.GOMAXPROCS 什么时候该调?
绝大多数情况不需要手动设——Go 默认行为已足够合理。只有两类例外值得干预:
- 程序启动早期就做了大量初始化(比如预热 cache、加载大配置),且希望这部分不挤占后续业务 goroutine 的调度时间,可临时设低值(如
1)做完再恢复 - 混部环境(如容器限制了 CPU quota),
GOMAXPROCS高于实际可用核数会导致频繁上下文切换和调度抖动,应设为os.Getenv("GOMAXPROCS")或读取/sys/fs/cgroup/cpu/cpu.cfs_quota_us
硬编码 runtime.GOMAXPROCS(8) 是反模式——它会让程序在 4 核机器上浪费调度资源,在 16 核机器上又无法伸缩。
真正卡住多核利用率的,往往是锁和 GC
goroutine 多 ≠ 并行多。下面这些操作会把多个 P 绑定到同一个 M,或者让 P 长时间停摆:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
-
sync.Mutex争抢激烈时,goroutine 在gopark状态排队,P 空转等待唤醒 - 频繁分配小对象(如循环中
make([]int, 10))导致 GC 压力上升,STW 时间变长,P 被强制暂停 - 使用
time.Sleep或select {}做“伪忙等”,阻塞当前 M,而其他 M 无事可做
用 go tool trace 查看 Proc Status 图,如果看到大量 P 处于 Idle 或 Syscall 状态,但 CPU 利用率低,大概率是 I/O 或锁瓶颈,不是调度问题。
用 pprof 定位真实热点,别猜
想确认是不是 CPU 没跑满,先跑:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30然后输入
top -cum 看累计耗时最高的函数栈。常见陷阱:
- 误把
runtime.mallocgc当成业务瓶颈——其实是你在高频创建 map/slice,该复用就复用,该池化就池化 - 看到
runtime.futex占比高——说明sync.Mutex或 channel 收发成了瓶颈,考虑改用sync.RWMutex、无锁结构,或批量处理减少同步次数 - 发现大量
net.(*pollDesc).wait——I/O 密集型,此时提升 CPU 核数意义不大,该加连接池、该换异步 I/O 就得换
Go 调度器本身极少成为瓶颈;真正拖慢多核利用的,永远是你代码里那些隐式的串行依赖、共享状态和内存习惯。


















