What does calling runtime.GOMAXPROCS(n) at start-up turn off, and how do you get the default back?
answer
- zero means ask, not set
- an explicit value freezes something
- one call hands control back
- the old NumCPU idiom now costs you
- process-wide, so not a library's business
basics
~20 sSetting GOMAXPROCS explicitly — from code or from the environment variable — pins the value and stops the runtime's container-aware updates. runtime.GOMAXPROCS(0) only reports the current value, and runtime.SetDefaultGOMAXPROCS() hands control back to the runtime.
solid answer
~50 s`runtime.GOMAXPROCS(n)` with `n >= 1` sets the number of Ps and returns the previous setting; with `n <= 0` it changes nothing and just reports the current value, which is how you read it. The part people miss is the side effect: an explicit setting — whether from this call or from the `GOMAXPROCS` environment variable — opts the process out of the container-aware automatic updating, because the runtime treats it as a deliberate override it must not undo. Go 1.25 added `runtime.SetDefaultGOMAXPROCS()`, which recomputes the runtime's own default from the CPU count and cgroup limit and re-enables the updates. The practical rules: application code rarely needs to set it at all on a modern toolchain, the old `runtime.GOMAXPROCS(runtime.NumCPU())` start-up idiom is now actively harmful in containers because it reinstates the pre-container default, and a library must never set it, since the value is process-wide and belongs to the program's owner.
code
go · 7 linescurrent := runtime.GOMAXPROCS(0) // 0 reports without changing anything
fmt.Println("GOMAXPROCS is", current)
prev := runtime.GOMAXPROCS(4) // pin to 4 Ps; automatic updates stop
fmt.Println("was", prev)
runtime.SetDefaultGOMAXPROCS() // recompute the default; updates resumego deeper
Remember that a zero or negative argument to runtime.GOMAXPROCS only reads the value, and that setting it returns the previous setting rather than the new one.
Explain the side effect: any explicit setting pins the value and turns off the runtime's automatic, container-aware adjustment, and name the call that restores it.
Be the person who greps a legacy service for GOMAXPROCS calls during a container migration, and who insists that any deliberate override is logged with its reason.
Set the org rule — libraries never touch it, only the start-up path may, overrides need a written justification — and make it enforceable in review rather than folklore.
## The API surface There are three ways the value gets decided, and they interact: 1. **The runtime's default.** Computed at start-up and, on Go 1.25+ on Linux, refreshed periodically from the CPU count and the cgroup CPU bandwidth limit. 2. **The `GOMAXPROCS` environment variable.** Read once at start-up; an explicit override. 3. **`runtime.GOMAXPROCS(n int) int`.** With `n >= 1`, sets the number of Ps and returns what it was before. With `n <= 0`, it sets nothing and returns the current value — that zero-argument form is the read accessor, and it is the one you want in a start-up log or a health endpoint. Go 1.25 adds a fourth entry point, `runtime.SetDefaultGOMAXPROCS()`, which takes no arguments: it recomputes the runtime's own default and, importantly, puts the process back under automatic updating. ## The side effect that surprises people On a container-aware runtime, the default is not a one-time computation — it is a policy the runtime keeps applying as the cgroup limit moves. An explicit setting cancels that policy. The reasoning is straightforward: if you said 8, the runtime silently changing you to 4 an hour later would be a worse bug than the staleness. So the moment either the environment variable or a `runtime.GOMAXPROCS(n >= 1)` call takes effect, the value is frozen at what you asked for until something sets it again. That gives an old habit a new cost. The idiom ```go runtime.GOMAXPROCS(runtime.NumCPU()) ``` was, for years, a harmless no-op that made the default explicit — it set exactly what the runtime would have chosen anyway. Inside a container on a modern runtime it is the opposite of harmless: it reinstates the *pre-container* rule, discarding the cgroup limit the runtime had just taken into account, and it disables the updates as well. Every one of those lines in an old codebase is now a latent throttling bug, and grepping for it is a cheap, high-yield audit. ## Getting the default back `runtime.SetDefaultGOMAXPROCS()` is the undo. It is useful in two shapes: - A program that needs a temporary override — say, a single-threaded start-up phase — and wants the runtime back in charge afterwards. - A program embedded in something that pinned the value before `main` ran and wants to restore fleet-standard behaviour deliberately. It is not a knob for a library to reach for either. Restoring the default is as much a process-wide policy decision as overriding it. ## Why libraries must stay out of it GOMAXPROCS is a single process-wide integer. A library that sets it in an `init` function, or in a `New…` constructor, silently reconfigures a program it knows nothing about — including how its importer's other dependencies behave and how the deployment's CPU quota is honoured. The effect is invisible in review because the call is not in the application's own code. The convention is firm: only `main` (or the process's start-up path) may set GOMAXPROCS, and even there the modern advice is usually not to. ## When setting it explicitly is still right There are honest cases: - **Deterministic tests and benchmarks**, where you want a fixed parallelism to compare runs. Prefer scoping this to the test rather than the binary. - **Reproducing a bug** that only appears at a particular P count. - **A workload that deliberately over-subscribes its quota** — bursty, latency-insensitive batch work that would rather absorb throttling than leave burst capacity unused. That is a real position, but it should be a written decision with numbers behind it, not an accident inherited from a five-year-old start-up file. In every one of those cases, log what you set and why. An operator staring at an unexplained P count has no way to tell a decision from an oversight.
- Why is `runtime.GOMAXPROCS(runtime.NumCPU())` a bug inside a container today?`runtime.NumCPU()` reports the node's usable CPUs and knows nothing about the cgroup CPU quota, so the call overwrites the container-aware default with the very number that default exists to avoid — and, being explicit, it also stops the runtime from correcting the value later.
- How do you read GOMAXPROCS without changing it?`runtime.GOMAXPROCS(0)`. Any argument less than one leaves the setting alone and returns the current value. Logging it next to `runtime.NumCPU()` at start-up is the cheapest way to see what the runtime actually chose inside a container.
- A dependency sets GOMAXPROCS in its init function. What do you do?Treat it as a defect and report it: the value is process-wide and belongs to the program's owner. As a stopgap, call `runtime.SetDefaultGOMAXPROCS()` early in `main` to restore the runtime's default, and log that you did so, since the fix is otherwise invisible to whoever debugs it next.
saying these in an interview costs you the question
- Thinks runtime.GOMAXPROCS(0) disables parallelism
- Sets GOMAXPROCS to runtime.NumCPU as defensive boilerplate
- Unaware that an explicit setting stops the container-aware updates
- Calls GOMAXPROCS from library init code
- Expects the GOMAXPROCS environment variable to be re-read while running