GOMAXPROCS 必须设为逻辑核数,否则 Go 并行能力被锁死;容器中需结合 cgroup 限值计算可用核数;CPU 密集任务应分块而非滥启 goroutine;网络服务需多进程+SO_REUSEPORT 才能压满多核;GC 和锁竞争会严重拖垮实际并行度。
GOMAXPROCS 必须设对,否则 Go 进程永远只跑在一个核上——哪怕你机器有 64 核,runtime.GOMAXPROCS(0) 返回 1 就说明并行能力被锁死了。
检查并设置 GOMAXPROCS 到逻辑核数
go 1.5+ 默认值是 runtime.numcpu(),但容器、ci 环境或旧版启动脚本可能覆盖它。别猜,先验证:
- 在
main()第一行加fmt.Println("GOMAXPROCS =", runtime.GOMAXPROCS(0)) - 若输出为 1,立刻补上
runtime.GOMAXPROCS(runtime.NumCPU()) - Docker 中注意:
runtime.NumCPU()返回宿主机核数,需结合/sys/fs/cgroup/cpu/cpu.cfs_quota_us和cpu.cfs_period_us手动算可用核数(例如 quota=200000, period=100000 → 2 核)
CPU 密集任务必须按核分块,别乱起 goroutine
起 1000 个 goroutine 做纯计算,实际只有 GOMAXPROCS 个能并行,其余排队等 P,还拉高调度开销和缓存失效率。
- 正确做法:用
runtime.NumCPU()算出 N,把大数组/大循环切为 N 段,每段一个 goroutine 独立算 - 避免小粒度任务:单段至少耗时几毫秒以上,否则调度成本 > 计算收益
- 别用 channel 汇总中间结果——同步阻塞会卡住整个 worker;改用
sync.WaitGroup+ 全局 slice 存结果
网络服务无法靠单进程多协程突破单 poller 瓶颈
HTTP/TCP 服务里起多个 goroutine 调 http.Server.Serve(),epoll_wait 仍只在单个 OS 线程里跑,CPU 永远打不满一个核。
-
runtime.LockOSThread()在这里完全无效,还可能让 goroutine 死锁在系统调用上 - 实测中,16 个 goroutine 共享一个
net.Listener,top显示始终只有一个线程在跑syscall.Sys - 真要压满多核网络吞吐,必须走多进程:用
SO_REUSEPORT(Linux 3.9+)或反向代理分发连接
混合型任务要防 GC 和锁拖垮并行效率
看似 CPU 密集的代码,如果频繁 new 对象或抢同一把 mutex,runtime.GC 的 STW 阶段会暂停所有 P,实际并行度断崖下跌。
- 用
sync.Pool复用大对象(如 bytes.Buffer、JSON 解析器) - 避免全局
map或chan成为竞争热点;高频写场景改用分片sync.Map或atomic.Value - pprof 看
runtime.mcall和runtime.gopark占比异常高?说明调度器在反复切换,不是 CPU 不够,是代码卡在锁或 channel 上
epoll_wait 和 runtime.mallocgc 同时出现在火焰图顶部时,问题已经不在协程数量上了。











