Problem Statement
Understand how goroutines are scheduled, what causes blocking, and best practices for managing concurrent workloads.
Key Interview Questions
Q1: What is the M:N scheduler?
Answer: Go multiplexes M goroutines onto N OS threads. This allows millions of goroutines on a few threads.
Q2: What is a "P" in the GMP model?
Answer: Processor/Context. It holds the local run queue and resources needed to execute goroutines. Number of Ps = GOMAXPROCS.
Q3: Why is a goroutine stack only 2KB vs 1-8MB for OS threads?
Answer: Goroutine stacks grow dynamically. They start small and expand as needed (up to 1GB). OS thread stacks are fixed at creation.
Q4: What happens when a goroutine does a blocking syscall?
// During blocking syscall (file I/O, etc.):
// 1. M (thread) blocks with the goroutine
// 2. P detaches from M
// 3. P finds/creates new M to run other goroutines
// 4. When syscall returns, G is re-queuedQ5: What is "Work Stealing"?
Answer: When a P's local run queue is empty, it steals half the goroutines from another P's queue to stay busy.
Q6: Is the Go scheduler preemptive?
Answer: - Pre-1.14: Cooperative (required function calls for preemption) - Go 1.14+: Asynchronously preemptive (uses signals to preempt tight loops)
Q7: How do you limit concurrent goroutines?
// Semaphore pattern
sem := make(chan struct{}, 10) // Max 10 concurrent
for _, item := range items {
sem <- struct{}{} // Acquire
go func(item Item) {
defer func() { <-sem }() // Release
process(item)
}(item)
}Q8: What is GOMAXPROCS?
import "runtime"
// Get current value
n := runtime.GOMAXPROCS(0)
// Set to 4 Ps
runtime.GOMAXPROCS(4)
// Default: runtime.NumCPU()Answer: Limits number of active Ps (logical processors). Defaults to number of CPU cores.
Q9: The Loop Variable Trap
// BUG: All goroutines print same value
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i) // Captures reference!
}()
}
// FIX 1: Pass as argument
for i := 0; i < 5; i++ {
go func(i int) {
fmt.Println(i)
}(i)
}
// FIX 2: Go 1.22+ - loop variables are per-iteration