Frontend Labs
⋯

28. The scheduler deep dive: GMP and work-stealing

28.1 The three letters: G, M, P

  • G (Goroutine): your lightweight function-in-flight, with its own small growable stack.
  • M (Machine): an OS thread — the thing the operating system actually schedules.
  • P (Processor): a scheduling context, one per GOMAXPROCS, each holding a local run queue of Gs ready to execute. An M must hold a P to run Go code.

28.2 Why this design

GOMAXPROCS (default: runtime.NumCPU()) caps how many Gs can execute Go code simultaneously, while thousands of Gs can be parked waiting on channels, I/O, or locks. When an M would block on a syscall, the runtime detaches it from its P and hands that P to another M, so blocking one goroutine on I/O never blocks the other goroutines scheduled on the same logical processor.

28.3 Work stealing

Each P has a local run queue; when a P's local queue empties, it steals half the Gs from another P's queue rather than going idle. This keeps all GOMAXPROCS processors busy under uneven workloads without a global lock on every scheduling decision.

28.4 Cooperative preemption and why it (mostly) doesn't matter to you

Since Go 1.14, the scheduler can preempt a long-running goroutine even without a function call or channel operation providing a natural checkpoint (async preemption via signals) — meaning a tight for {} CPU-bound loop no longer starves other goroutines on the same P the way it could in much older Go versions.

28.5 Real-world example

Setting GOMAXPROCS explicitly used to matter a lot in containers: older Go releases defaulted mostly from the host CPU count / affinity, not the container's CPU quota, so a container capped at 2 CPUs but running on a 64-core host could create too much runnable parallelism and hit CPU throttling. Go 1.25 added cgroup-aware default GOMAXPROCS behavior on Linux (GODEBUG=containermaxprocs=... controls it), and go.uber.org/automaxprocs covers older deployments — still worth checking explicitly any time you deploy Go into Kubernetes.

Applied in the series: Linux guide, Chapter 75: container-aware GOMAXPROCS (Go 1.25+) measured under cgroup CPU limits, including 13.7 s of throttling when GOMAXPROCS was forced too high.


Generated from real-life-golang-guide.md · 8 Oct 2026 16:36