Go

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, GOMAXPROCS defaults to the limit, rounded up.
  • On all platforms, the runtime re-checks periodically and updates GOMAXPROCS if the CPU count or the limit changes.
  • Both behaviors are off if you set the GOMAXPROCS environment variable or call runtime.GOMAXPROCS(n). runtime.SetDefaultGOMAXPROCS() brings back the automatic value.
  • They can be turned off with GODEBUG=containermaxprocs=0 and updatemaxprocs=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 automaxprocs when you move to 1.25; it sets GOMAXPROCS explicitly, which switches off the runtime's periodic updates.
  • Size CPU-bound worker pools from runtime.GOMAXPROCS(0), not from runtime.NumCPU().

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions