Under The Hood
backend go Go 1.21+ (preemption improvements in 1.14+)

Go scheduler: GMP model

Last updated
Prerequisites:
Go goroutines and channels basics
Understanding of OS threads vs user-space scheduling
  • go
  • goroutine
  • scheduler
  • gmp
  • concurrency
  • runtime

Read at your depth

The practical view

go func() { ... }() starts a goroutine. Goroutines are lightweight user-space threads: they start with a tiny stack (2KB, grows) and are multiplexed by the Go runtime onto a small set of OS threads. The runtime scheduler uses the GMP model: G is a goroutine, M is an OS thread (machine), P is a processor (logical CPU, default GOMAXPROCS = number of cores). A goroutine blocks on a channel/syscall; the runtime parks its M or the G and schedules another G. You do not manage threads; the runtime balances work across Ps.

Legacy vs modern

Blocking the main thread vs goroutine-parallel work

Serialized sequential work leaves cores idle; distributing across goroutines uses GOMAXPROCS workers through the GMP scheduler.

before → after
Sequential work
var total int64
for _, n := range nums {
  total += heavy(n) // one core does everything
}
Worker-pool parallelism
jobs := make(chan int); results := make(chan int64)
// N workers consume jobs; the GMP scheduler keeps all Ps busy

Interview gotchas

Context

This checks whether the candidate understands cooperative vs preemptive scheduling in Go — a frequent senior-level probing point.

The mechanical answer

Before Go 1.14, the scheduler was mostly cooperative: a G yielded only at safe points — function calls, channel operations, allocation, etc. A tight CPU loop with no function calls (like for {}) never hit a safe point, so it could starve other goroutines indefinitely on its P. Go 1.14 added asynchronous preemption: the runtime sends a SIGURG signal to the thread running the misbehaving G, which interrupts the loop at a safe point inserted by the compiler and allows the scheduler to run another G. Stack-scan safe points and the preemption flag in the G struct coordinate the interrupt. Preemption is still not fully arbitrary — some C code and certain runtime internals are non-preemptible.

Trap

The trap answer is 'goroutines are preempted by the OS like threads'. Goroutines are user-space; OS-level preemption does not apply. Another trap: 'tight loops never yield, so they hang forever' — that is true pre-1.14 and demonstrably wrong now, but the nuance (async preemption via signals, safe points, non-preemptible regions) is what the interviewer wants. Mentioning runtime.Gosched() as the cooperative alternative shows you know both mechanisms.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.