GOMAXPROCS — Specification¶
Table of Contents¶
- Scope
- Public API
- Default Behaviour
- Environment Variable
- Precedence Rules
- Cgroup Detection
- STW Semantics
- Concurrency Guarantees
- Platform-Specific Behaviour
- Compatibility and History
- Self-Assessment
Scope¶
This document specifies the observable behaviour of runtime.GOMAXPROCS and the runtime mechanisms that derive its default. It covers Go 1.5 through Go 1.23, calling out version-introduction points for changes. It does not document internal runtime data structures (p, allp); see professional.md for that.
The specification reflects the documented runtime package, the GODEBUG envvar reference, and observable runtime behaviour. Where the source disagrees with the documentation, the source wins — but those cases are rare and noted.
Public API¶
The function lives in runtime:
Contract:
- If
n > 0, the function sets the parallelism cap tonand returns the previous value. - If
n <= 0, the function returns the current value without changing it. - The returned value is always the value that was in effect immediately before the call.
- Setting to the current value is a no-op (no STW, no observable change).
Thread safety: the function is safe to call from any goroutine. Internally it acquires sched.lock briefly.
Return value precondition: the return value is meaningful only after main.main has started. Before that (during runtime.main initialisation), the value may be the unconfigured default.
Default Behaviour¶
Before Go 1.5¶
The default value was 1. Programs needed to explicitly call runtime.GOMAXPROCS(N) or set the env var to enable parallelism.
Go 1.5 and later¶
The default value is runtime.NumCPU().
runtime.NumCPU() definition:
- On Linux: the number of logical CPUs available to the calling thread, as returned by
sched_getaffinity— and as constrained by the process's CPU cgroup quota, since Go 1.16 (cgroup v1) / 1.18 (cgroup v2). - On Darwin (macOS): the number of logical CPUs visible to the process, as returned by
sysctlbyname("hw.logicalcpu"). - On Windows: the number of logical processors in the active processor group.
- On other platforms: the platform-native CPU count.
NumCPU() is computed once at startup and cached. Subsequent calls return the cached value.
Environment Variable¶
GOMAXPROCS may be set via the GOMAXPROCS environment variable:
- Read by the runtime once at startup.
- Parsed as a base-10 signed integer.
- If the value is a positive integer, it is used as the initial
GOMAXPROCS, overriding the cgroup-derived default. - If the value is zero, negative, or unparsable, it is ignored and the default is used.
- The env var is honoured before any code in
main.mainruns.
Important: if GOMAXPROCS env var is set, it overrides cgroup detection. A pod with cpu: 1 but GOMAXPROCS=64 will see runtime.GOMAXPROCS(0) == 64.
Precedence Rules¶
In order from highest precedence to lowest:
- Explicit calls to
runtime.GOMAXPROCS(n)in user code. Last write wins. GOMAXPROCSenvironment variable at process startup.- Cgroup-derived default (Linux ≥ 1.16/1.18). Computed from
cpu.maxorcpu.cfs_quota_us/cpu.cfs_period_us. - CPU affinity (
sched_getaffinity) if cgroup detection produces no usable result. - Platform CPU count as a final fallback.
automaxprocs operates at level 1 (it calls runtime.GOMAXPROCS) but in an init() function — so it runs before main.main, after the env-var-derived default has been applied. If the env var is set, automaxprocs does not override (by default).
Cgroup Detection¶
cgroup v1¶
The runtime reads:
/sys/fs/cgroup/cpu,cpuacct/<group>/cpu.cfs_quota_us/sys/fs/cgroup/cpu,cpuacct/<group>/cpu.cfs_period_us
The <group> path is determined by parsing /proc/self/cgroup and /proc/self/mountinfo.
- If
cpu.cfs_quota_usis-1, no quota; fall back to affinity. - Otherwise,
ceil(quota_us / period_us), clamped to>= 1.
Introduced in Go 1.16 (February 2021).
cgroup v2¶
The runtime reads:
/sys/fs/cgroup/cpu.max(or the subpath determined from/proc/self/cgroup).
Format: two space-separated tokens — <quota> <period>. A quota of max means no limit.
If a quota is set, compute ceil(quota / period), clamped to >= 1.
Introduced in Go 1.18 (March 2022).
Failure modes¶
If cgroup files are unreadable (permission errors, missing mounts), the runtime falls back silently to affinity-based detection. There is no error or warning surfaced to user code.
The automaxprocs library will optionally log a warning. The standard runtime will not.
STW Semantics¶
Calling runtime.GOMAXPROCS(n) with n > 0 and n != current:
- Triggers a stop-the-world pause.
- All running goroutines are paused at safe points.
- The runtime resizes the internal P table (adds or removes
pstructs). - The world is restarted.
Cost: typically tens to hundreds of microseconds for a healthy process. The cost grows with the number of goroutines (more safe points to wait for) and is bounded by async preemption (Go 1.14+).
Visibility in traces: STW for GOMAXPROCS is recorded with reason stwGOMAXPROCS. Visible in runtime/trace and GODEBUG=gctrace=1 output.
No-op fast path: runtime.GOMAXPROCS(n) with n == current does not STW. It only acquires sched.lock briefly to read the current value.
Special platforms:
- On
wasip1andjs/wasm,GOMAXPROCSis always 1; the function returns 1 and ignores the argument.
Concurrency Guarantees¶
- Multiple goroutines may call
GOMAXPROCSconcurrently. The runtime serialises them viasched.lock. Each call sees a consistent snapshot. - Reads from
runtime.GOMAXPROCS(0)are atomic and lock-free in the fast path (the lock is acquired only to provide the precise "previous value" semantics on writes). - A call to
runtime.GOMAXPROCS(0)is guaranteed to return a value consistent with the most recent completed write across all goroutines.
Platform-Specific Behaviour¶
Linux¶
Full support. cgroup-aware defaults. sched_getaffinity respected.
Darwin (macOS)¶
No cgroup detection (the OS does not use cgroups). NumCPU returns logical CPUs as visible to the process. Default GOMAXPROCS = NumCPU.
Windows¶
No cgroup equivalent. NumCPU returns the count from GetActiveProcessorCount. Default GOMAXPROCS = NumCPU.
Windows job objects can limit CPU; Go does not detect this. Set GOMAXPROCS manually.
FreeBSD, OpenBSD, NetBSD¶
NumCPU returns the platform-native count. No quota detection.
wasip1, js/wasm¶
Always single-threaded. GOMAXPROCS = 1, immutable.
Solaris / illumos¶
NumCPU via sysconf(_SC_NPROCESSORS_ONLN). No quota detection.
Android¶
Same as Linux for cgroup detection. App-level CPU governors are not detected.
Compatibility and History¶
| Version | Date | Change |
|---|---|---|
| 1.0 | 2012-03 | GOMAXPROCS exists; default = 1. |
| 1.1 | 2013-04 | GMP scheduler; P count tracks GOMAXPROCS. |
| 1.5 | 2015-08 | Default = NumCPU(). |
| 1.14 | 2020-02 | Async preemption bounds STW for GOMAXPROCS resizes. |
| 1.16 | 2021-02 | cgroup v1 quota detection on Linux. |
| 1.18 | 2022-03 | cgroup v2 quota detection on Linux. |
| 1.21 | 2023-08 | runtime/metrics exposes /sched/gomaxprocs:threads. |
| 1.22 | 2024-02 | Minor improvements to cgroup parsing robustness. |
| 1.23 | 2024-08 | Documentation clarifications; no behaviour change. |
Deprecation guarantees: the Go 1 compatibility promise covers runtime.GOMAXPROCS. The function will not be removed. Its semantics may be refined but not broken.
Self-Assessment¶
- I can state the default
GOMAXPROCSvalue for each Go version 1.0 to 1.23. - I know the precedence order: explicit call → env var → cgroup → affinity → CPU count.
- I know which cgroup files Go reads and when (v1 from 1.16, v2 from 1.18).
- I can describe the STW semantics including the no-op fast path.
- I know platform-specific defaults for Linux, Darwin, Windows, and wasm.
- I can read
runtime.GOMAXPROCS(0)and explain why the argument must be non-positive.
This specification is the contract; the runtime is the implementation. When the two disagree, file an issue at golang.org/issue. As of Go 1.23, the contract above is accurate.