
What the Go scheduler does when your goroutine blocks
A blocking syscall costs an OS thread. A blocking network read costs nothing. Measured with 200 goroutines, GOMAXPROCS=2 and a thread count.
A Go publication by Nolan Keir
Deep dives into the Go runtime, concurrency, performance, and the trade-offs behind clean abstractions.

A blocking syscall costs an OS thread. A blocking network read costs nothing. Measured with 200 goroutines, GOMAXPROCS=2 and a thread count.
GOGC=100 with a 256 MiB limit produced exactly what GOGC=off produced: same collections, same CPU share, same throughput. The tighter rule binds, the other goes quiet.
A static route through net/http's mux costs 85.6 ns and nothing on the heap. A path that matches no pattern costs 2125.5 ns and 62 allocations.
One goroutine: 2.5 ns for the mutex, 172.6 ns for the channel. Sixteen goroutines with real work: the channel wins by less than the harness's own error.
Reaching a 1 MiB stack copied it nine times. The copying cost 136µs of CPU that -benchmem reports as 144 bytes, because stack memory is not heap memory.
Two sub-benchmarks calling the same function with the same argument came back 1.10% apart at p=0.000. What a benchmark measures is not only your code.
GOMAXPROCS=1 with 200 goroutines blocked in read(2) produced 202 OS threads. Measured on go1.27.0, alongside the container case Go 1.25 changed.
Series4 parts
Starts with: What the Go scheduler does when your goroutine blocks