How is GOMAXPROCS chosen in containers, and what changed in Go 1.25?
Question 257HardGo 1.22 to 1.25
GOMAXPROCS is the number of Ps, which is the maximum number of goroutines executing Go code at the same moment. Goroutines blocked in syscalls do not count against it.
Before Go 1.25 it defaulted to the number of logical CPUs on the host. A pod limited to 2 CPUs on a 64-core node got GOMAXPROCS=64. The kernel's CFS quota then throttled the process: its 64 threads burned the whole 2-CPU quota early in each 100ms period and sat idle for the rest, which caused large tail-latency spikes and GC pauses. The usual workaround was go.uber.org/automaxprocs.
Go 1.25 (when go.mod declares go 1.25 or later):
- On Linux, the default takes the cgroup CPU bandwidth limit into account. If the limit is lower than the number of logical CPUs,
GOMAXPROCSdefaults to the limit, rounded up. - On all platforms, the runtime re-checks periodically and updates
GOMAXPROCSif the CPU count or the limit changes. - Both behaviors are off if you set the
GOMAXPROCSenvironment variable or callruntime.GOMAXPROCS(n).runtime.SetDefaultGOMAXPROCS()brings back the automatic value. - They can be turned off with
GODEBUG=containermaxprocs=0andupdatemaxprocs=0.
fmt.Println(runtime.GOMAXPROCS(0)) // 0 = query without changing
fmt.Println(runtime.NumCPU()) // logical CPUs usable by the process
Gotchas:
- Only CPU limits are considered, not Kubernetes CPU requests. A pod with requests and no limit still gets the node's CPU count.
- Remove
automaxprocswhen you move to 1.25; it setsGOMAXPROCSexplicitly, which switches off the runtime's periodic updates. - Size CPU-bound worker pools from
runtime.GOMAXPROCS(0), not fromruntime.NumCPU().
More on Concurrency Patterns & sync
- Q255What does this print? Can recover in main catch a panic from another goroutine?
- Q256What is false sharing, how does it hurt concurrent Go code, and how do you fix it?
- Q258Implement a pub/sub broadcaster where one slow subscriber cannot block the others.