Go scheduler work-stealing and blocking syscalls
knowledge of the Go runtime scheduler internals.
the GMP model runs goroutines (G) on OS threads (M) attached to logical processors (P); idle P's steal half of another P's run queue; on a blocking syscall the M detaches with its G.
WHAT THIS TESTS It checks whether you understand the GMP architecture of the Go scheduler, the work-stealing mechanism that balances load, and how blocking system calls are handled without stalling other goroutines.
A GOOD ANSWER COVERS Go's scheduler uses three entities. A G is a goroutine, the unit of work with its own small stack. An M is a machine, an OS thread that actually executes code. A P is a logical processor, a scheduling context that holds resources needed to run Go code; the number of P's is set by GOMAXPROCS and bounds parallelism. To run Go code, an M must be associated with a P. Each P owns a local run queue of runnable goroutines, and there is also a global run queue. The scheduler prefers the cheap local queue. When a P's local queue is empty, work stealing kicks in: the P checks the global queue and the network poller, and if still idle, it picks a random victim P and steals about half of that P's runnable goroutines into its own queue, which keeps logical processors from sitting idle while others are overloaded.
BLOCKING SYSTEM CALLS When a goroutine enters a blocking system call, its M blocks in the kernel along with it. So other goroutines on that P are not starved, the runtime detaches the P from the blocked M and hands it to another M (creating or reusing a thread), which resumes running the remaining goroutines. When the syscall returns, the original M tries to reacquire a P; if none is free, its goroutine is placed back on a run queue and the M parks.
COMMON WRONG ANSWERS Conflating M and P, or saying GOMAXPROCS limits the number of OS threads (it limits P's, not M's). Claiming a blocking syscall freezes all goroutines. Saying goroutines map one-to-one to threads.
LIKELY FOLLOW-UPS How do non-blocking network calls use the netpoller instead of blocking an M? What is preemption and how did asynchronous preemption change long-running loops? Why steal half rather than one?
ONE CONCRETE EXAMPLE With GOMAXPROCS set to 4, four P's run goroutines across however many M's are needed. If one goroutine calls a blocking read on a file, its M blocks in the kernel; the runtime hands that P to another thread so the other goroutines queued on it keep executing. Meanwhile, if one P drains its queue while another has many runnable goroutines, the idle P steals half of them, balancing the load.
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.