Lesson 23 · Testing, tooling & performance
The runtime: scheduler & GC
How Go runs millions of goroutines and reclaims memory — the finale.
Your win: explain the GMP scheduler and Go's concurrent garbage collector well enough to answer "how does Go run so many goroutines cheaply?" — the under-the-hood question that closes strong interviews.
The GMP scheduler
Goroutines aren't OS threads (Lesson 14). The Go runtime multiplexes many goroutines onto a few threads using three entities:1
G — Goroutine
Your concurrent task; a tiny growable stack. Cheap to create; millions are feasible.
M — Machine
An OS thread. It executes Go code only while holding a P; may block on syscalls.
P — Processor
A logical processor with a local run queue. There are exactly GOMAXPROCS of them (≈ CPU cores).
GOMAXPROCS logical processors (P). Each P has a local run queue; when it
empties, it steals work from another P. That's how Go gets massive
concurrency with a handful of threads — and why launching a goroutine is nearly free.
Since Go 1.14 the scheduler is also preemptive: a goroutine running a tight loop can be interrupted so others get a turn (no more "one hot goroutine starves the rest"). A blocking syscall hands the P to another M so work continues.
The garbage collector
Go is garbage-collected: you allocate, the runtime reclaims. Its GC is a concurrent, tri-color mark-and-sweep collector tuned above all for low pause times — it runs alongside your program, so stop-the-world pauses are sub-millisecond, not the hundreds of ms of older GCs.2
- Stack vs heap — escape analysis (Lesson 22) keeps what it can on the stack (free); only escaping values cost the GC anything.
GOGC— tunes how often GC runs (default 100 = collect when the heap doubles). Higher = less CPU on GC, more memory; lower = the reverse.GOMEMLIMIT— a soft memory cap that makes the GC work harder as you approach it, to avoid OOM.
GOMAXPROCS, GOGC,
GOMEMLIMIT), and know how to observe them (Lesson 22's profiles,
the GC guide's trace tools). "I'd measure before tuning" beats "I'd set GOGC to X."
A Guide to the Go Garbage Collector + "Getting to Go"
The official GC guide (mental model + tuning) and Rick Hudson's keynote on how the low-pause collector was built. For the scheduler, the runtime docs + the Scalable Scheduler design doc.
→ go.dev/doc/gc-guide ·
go.dev/blog/ismmkeynote
→ pkg.go.dev/runtime (GOMAXPROCS, GC knobs)
Check yourself (from memory)
Q1. In the GMP model, P is…
GOMAXPROCS of them. G = goroutine, M = OS thread.
Q2. Go schedules goroutines using…
Q3. Go's garbage collector is designed primarily for…
GOMAXPROCS logical processors (P) — M:N scheduling with
per-P run queues and work-stealing. Goroutines have tiny growable stacks and switch in
user space (no OS thread each). A concurrent, low-pause GC then reclaims their memory
without long stop-the-world. So "just launch a goroutine" is idiomatic and cheap — the
runtime handles it.1. runtime package (GOMAXPROCS); the Scalable Go Scheduler design doc; Concurrency is not Parallelism.
2. A Guide to the Go Garbage Collector; Getting to Go: The Journey of Go's Garbage Collector.