GOMAXPROCS 的名字来自“Go maximum processors”,它告诉运行时允许多少个 P,也就是最多允许多少条线程同时执行普通 Go 代码。它不限制 G 的总数,也不限制因系统调用而阻塞的 M 数量。
不是 G 的上限
即使 GOMAXPROCS=2,也可以创建十万条 G;它们只是在等待、排队和运行之间切换。
不是 M 的硬上限
如果许多线程阻塞在系统调用里,运行时仍可能创建更多 M,保证有线程拿 P 执行 Go 代码。
是 Go 代码并行度
GOMAXPROCS=2 时,最多两条线程同时执行普通 Go 代码。
Go 1.25 的容器默认值变化
Go 1.24 及之前,默认值主要是机器可见的逻辑 CPU 数。Go 1.25 起,如果程序没有手工设置,Linux 上的运行时会综合逻辑 CPU、CPU 亲和性和容器 cgroup CPU 限额选择默认值,并周期性检查资源变化。cgroup 是 Linux 用来限制和统计一组进程资源的内核机制,这里负责表达容器可用的 CPU 配额。
工程判断:不要机械地把 GOMAXPROCS 调到 goroutine 数,也不要默认“越大越快”。CPU 密集任务超过有效 CPU 并行度后,通常只会增加线程竞争和切换;I/O 密集服务可以有很多 G,但同时执行 Go 代码的 P 仍应贴近实际 CPU 资源。
九、源码主线:从 go 语句一路跟到真正执行
第一次读调度器源码不要从几千行的 proc.go 顶部顺序往下读。按一条 G 的生命周期追调用链,更容易建立完整地图。
package main
import (
"fmt""runtime""sync""sync/atomic"
)
funcmain() {
runtime.GOMAXPROCS(2)
var running, peak atomic.Int32
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
n := running.Add(1)
for {
old := peak.Load()
if n <= old || peak.CompareAndSwap(old, n) { break }
}
for x := 0; x < 30_000_000; x++ {}
running.Add(-1)
}()
}
wg.Wait()
fmt.Println("观测到的峰值:", peak.Load())
}