Golang / Golang Internals and Memory Management Interview Questions
How does Go's goroutine scheduler work? Explain the GMP model.
Go uses a cooperative/preemptive M:N scheduler — M goroutines multiplexed onto N OS threads, where N defaults to GOMAXPROCS (number of logical CPUs). The scheduler uses three key entities:
| Entity | Symbol | Description |
|---|---|---|
| Goroutine | G | The logical unit of work — a user-space green thread with its own stack |
| Machine (OS thread) | M | An OS thread that executes Go code; managed by the runtime |
| Processor | P | A logical CPU context; holds a local run queue of goroutines waiting to run |
// GOMAXPROCS controls the number of P's (and thus OS threads active) import "runtime" runtime.GOMAXPROCS(4) // use 4 OS threads for parallel execution fmt.Println(runtime.GOMAXPROCS(0)) // 0 = query without changing // Scheduler events that cause a context switch: // 1. Goroutine blocks on channel send/receive // 2. Goroutine blocks on system call (M parks, P finds another M) // 3. go statement (new G added to local run queue of current P) // 4. runtime.Gosched() â voluntary yield // 5. Function call (Go 1.14+: asynchronous preemption via signals) // Work stealing: when P's local queue is empty, // it steals half the goroutines from another P's queue // Global run queue: accessed when local queue has > 256 Gs, // or periodically to ensure fairness // View scheduler decisions // GOTRACE=scheduler ./myapp â not a real flag, but: // go tool trace trace.out â after: f, _ := os.Create("trace.out") // trace.Start(f); ...; trace.Stop()
System call handling: When a goroutine makes a blocking system call (e.g., reading a file), the M detaches from its P. The P then attaches to another idle M (or creates a new one), so other goroutines continue running. When the system call returns, the original M tries to reacquire a P; if none is available, it parks and the goroutine goes to the global queue.
Asynchronous preemption (Go 1.14+): the runtime sends SIGURG signals to goroutines that have run too long without a function call, forcing a context switch. This prevents a CPU-intensive goroutine from starving others even without cooperative yield points.
More Related questions...