How does the Go scheduler work with its GMP model?
Learn how Go's runtime scheduler uses the GMP model of goroutines, OS threads, and processors, with local run queues, work-stealing, and syscall handoff.
Expected Interview Answer
The Go scheduler multiplexes many goroutines onto a small number of OS threads using the GMP model: G is a goroutine, M is an OS thread (machine), and P is a logical processor that holds a run queue and the resources needed to execute Go code.
Each P has a local run queue of runnable goroutines, and an M must acquire a P to run them. The number of Ps equals GOMAXPROCS, bounding parallelism. When a P's local queue empties, its M performs work-stealing from other Ps or the global queue. If a goroutine makes a blocking syscall, its M detaches with the goroutine while the P is handed to another M so other goroutines keep running. Preemption (cooperative at function calls, and asynchronous since Go 1.14) prevents any goroutine from monopolizing a P.
- Runs millions of cheap goroutines on few threads
- Work-stealing balances load across processors
- Blocking syscalls do not stall unrelated goroutines
- Parallelism bounded predictably by GOMAXPROCS
- Preemption avoids goroutine starvation
AI Mentor Explanation
Ps are the batting crease slots, Ms are the batters who must occupy a crease to face balls, and Gs are innings waiting to be played; when one batter leaves to treat an injury (syscall), a substitute takes the crease so play continues.
Step-by-Step Explanation
Step 1
Create goroutines (G)
Each go call creates a lightweight G with its own small, growable stack placed on a run queue.
Step 2
Assign processors (P)
GOMAXPROCS Ps exist, each owning a local run queue of runnable goroutines.
Step 3
Bind threads (M)
An OS thread M must acquire a P to execute the Gs in that P's queue.
Step 4
Work-stealing
When a P's local queue empties, its M steals goroutines from other Ps or pulls from the global queue.
Step 5
Handle blocking
On a blocking syscall the M and G detach; the P is released to another M so other goroutines keep running.
Step 6
Preempt long runners
Cooperative and asynchronous preemption stop any goroutine from hogging a P indefinitely.
What Interviewer Expects
- Correct meaning of G, M, and P
- That P count equals GOMAXPROCS and bounds parallelism
- Local run queues plus work-stealing
- How blocking syscalls hand off the P
- Awareness of goroutine preemption
Common Mistakes
- Saying M is the goroutine and G is the thread
- Claiming there is one global run queue only
- Thinking a blocking syscall stalls all goroutines
- Ignoring GOMAXPROCS as the parallelism bound
- Assuming goroutines are never preempted
Best Answer (HR Friendly)
“Go runs huge numbers of tiny tasks called goroutines on just a few operating-system threads. It uses logical processors, each with its own to-do list, and threads pick up work from them — stealing from busy ones when idle and swapping in when a task blocks — so the CPU stays efficiently used.”
Code Example
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("GOMAXPROCS (P count):", runtime.GOMAXPROCS(0))
fmt.Println("NumCPU:", runtime.NumCPU())
for i := 0; i < 4; i++ {
go work(i) // each G is queued onto a P's local run queue
}
runtime.Gosched() // cooperatively yield the current goroutine
fmt.Println("Goroutines:", runtime.NumGoroutine())
}
func work(id int) { _ = id * id }Follow-up Questions
- What is work-stealing and why does it help?
- What happens to a P when its M makes a blocking syscall?
- How did asynchronous preemption in Go 1.14 change things?
- How does the global run queue differ from local queues?
- Why are goroutine stacks small and growable?
MCQ Practice
1. In the GMP model, what does P represent?
P is a logical processor holding a local run queue and resources; an M must acquire a P to run Go code, and the P count equals GOMAXPROCS.
2. When a goroutine makes a blocking syscall, what happens?
The blocking M keeps the G during the syscall, but the P is handed off to another M so other goroutines continue executing.
Flash Cards
G in GMP — A goroutine — a lightweight task with its own small, growable stack.
M in GMP — An OS thread (machine) that must hold a P to execute Go code.
P in GMP — A logical processor with a local run queue; count equals GOMAXPROCS.
Work-stealing — An idle P's M takes goroutines from other Ps or the global queue to balance load.
Blocking syscall handling — M and G detach; the P is released to another M so other goroutines keep running.