runtime.NumCPU()返回宿主机逻辑CPU数,不感知cgroup限制;容器中应通过/sys/fs/cgroup/cpu/计算实际配额核数,而非直接使用该值设置并发度或GOMAXPROCS。

runtime.NumCPU 返回的是逻辑 CPU 核心数,不是可用线程数
runtime.NumCPU 返回的是操作系统报告的逻辑 CPU 数量(例如超线程开启时,4 核 8 线程会返回 8),它不考虑当前系统负载、cgroup 限制或容器资源配额。在容器中运行时,它仍读取宿主机的值,而非 cpu.quota 或 cpuset.cpus 的实际约束。
这意味着:如果你在 Kubernetes Pod 中设置了 resources.limits.cpu: "2",runtime.NumCPU() 依然可能返回 64 —— 它无法感知容器运行时的 CPU 隔离。
- 不要直接用
runtime.NumCPU()设置 goroutine 池大小或工作协程数 - 若需适配容器环境,应优先读取
/sys/fs/cgroup/cpu/cpu.cfs_quota_us和/sys/fs/cgroup/cpu/cpu.cfs_period_us计算配额核数,或使用runtime.GOMAXPROCS的当前值作为更现实的上限参考 - 对 I/O 密集型任务,该值更无指导意义;并发度应由连接数、队列深度或业务 SLA 决定,而非 CPU 核数
别把 NumCPU 当作 GOMAXPROCS 的默认值
Go 运行时在启动时会将 GOMAXPROCS 设为 min(runtime.NumCPU(), 100)(1.21+ 版本),但这个默认行为只是调度器并行执行的上限,并不等于“推荐并发数”。你调用 runtime.NumCPU() 得到的值,和 runtime.GOMAXPROCS(0) 返回的当前值可能不同——后者才是调度器真正使用的并行度。
- 用
runtime.GOMAXPROCS(0)获取当前生效值,比runtime.NumCPU()更贴近实际调度能力 - 修改
GOMAXPROCS不影响NumCPU的返回值,两者完全独立 - 在单核机器上,
NumCPU()返回1,但设置GOMAXPROCS(10)仍合法(只是不会带来并行收益,只增加调度开销)
常见误用:用 NumCPU 控制 goroutine 并发数量
典型错误是写成:for i := 0; i 。这假设每个核心跑一个 goroutine 就是最优,但忽略了 Go 调度器的协作式特性、阻塞系统调用的抢占、以及 goroutine 本身的轻量性。
真实瓶颈往往不在 CPU,而在锁竞争、内存带宽、网络延迟或 GC 压力。盲目按核数起固定数量 worker,容易导致:channel 缓冲区溢出、goroutine 泄漏、或因过度并发反而降低吞吐。
- 对生产者-消费者模型,worker 数应基于处理延迟与输入速率动态调整,例如用
semaphore.NewWeighted(int64(runtime.NumCPU()))做粗粒度限流,再配合背压反馈 - HTTP server 默认已用 goroutine 复用模型,无需也不应手动限制 handler 并发数
- 数据库连接池、HTTP client Transport 的
MaxIdleConns等参数,远比NumCPU更关键
真正该关心的是:什么时候不该用 NumCPU
几乎所有需要“控制并发规模”的场景,都不该直接依赖 runtime.NumCPU()。它唯一可靠用途是做粗略初始化提示(比如日志里打一句 “detected runtime.NumCPU() CPUs”),或某些极底层的内存对齐/分片预估(如 sync.Pool 的 shard 数量)。
- 批处理任务分片:优先按数据量或耗时预估分片数,而不是核数;核数仅作上限兜底(如
min(estimatedShards, runtime.NumCPU()*2)) - 测试基准:用
runtime.NumCPU()固定GOMAXPROCS可提升结果可比性,但这是为了控制变量,不是因为它代表“最优” - 跨平台构建脚本中判断是否启用并行编译:这时它才接近原始设计意图——反映硬件并行能力
最常被忽略的一点:即使你知道了准确的可用 CPU 核数,goroutine 的并发效率也高度依赖你的代码是否真正在做 CPU-bound 工作。多数 Go 服务的瓶颈根本不在这里。

















