Back to series

Goroutines and the scheduler

How Go schedules concurrent work — and why you rarely tune GOMAXPROCS anymore.

Table of Contents

If you write Go long enough, “just add a goroutine” becomes muscle memory. Understanding what happens next is what separates intermediate from beginner code.

The model in one paragraph

Goroutines are cheap stack-switched tasks. The runtime multiplexes them onto OS threads (M) through processors (P). When a goroutine blocks on I/O, the runtime parks it and reuses the thread — no thread-per-request waste.

Starting work

results := make(chan int, 3)

for i := 1; i <= 3; i++ {
    go func(n int) {
        results <- n * n
    }(i)
}

sum := 0
for i := 0; i < 3; i++ {
    sum += <-results
}

When to care

  • Default GOMAXPROCS matches your cores; override only with measured reason
  • Bounded buffers (chan T, n) are your backpressure tool
  • If you can’t explain who closes a channel, redesign it

Part 2 builds on this with context and cancellation — the other half of production concurrency.