Go GMP Scheduler

G (Goroutine): 2KB starting stack, growable. ~1µs to create. Cheap enough to create one per connection.

M (Machine): OS thread. Executes G's. Can exceed GOMAXPROCS when M's are blocked on syscalls.

P (Processor): logical processor, local run queue (256 capacity). Exactly GOMAXPROCS P's (default = CPU cores). M must hold P to run Go code. P holds mcache.

graph TD subgraph p1["P1 (holds mcache)"] m1["M1 (OS thread)"] --> rq1["local run queue: G1, G2, G3"] end subgraph p2["P2"] m2["M2 (OS thread)"] --> rq2["local run queue: EMPTY"] end rq2 -.->|"work stealing: steal half of P1's queue"| rq1 blocked["M3 blocked on syscall"] -.->|"detaches its P"| p3["P freed up for another M"]

Scheduling loop: pop G from local queue → run until block/yield → repeat. Empty queue: check global queue → steal half from another P (work stealing).

Work stealing: P steals half of another P's queue when idle. Automatic load balancing without central coordinator.

Blocking syscall: M blocked on syscall detaches P → another M (created if needed) acquires P → other goroutines continue. Syscall completes → M tries to reacquire P; if none available, G goes to global queue, M goes idle.

Network I/O: goroutine parked, fd registered with epoll, M runs other goroutines, goroutine unparked when data ready. No OS thread blocked.

Preemption (Go 1.14+): SIGURG sent to M after 10ms → goroutine preempted asynchronously at safe points. Prevents CPU-bound goroutines from starving others.

Common pitfall

A single goroutine stuck in a tight loop with no function calls at all (pure arithmetic, no channel ops, no allocations) was unpreemptible before Go 1.14's async preemption — and even after, async preemption has edge cases (certain tight assembly loops) where it still can't inject a preemption point. In practice this means a genuinely pathological CPU-bound loop can still starve other goroutines on the same P longer than expected; the fix is the same as always for CPU-bound work — hand it to a dedicated worker pool sized to GOMAXPROCS, not an unbounded number of concurrent goroutines competing for the same limited P's.