How does a goroutine's stack grow? What changed from segmented to contiguous stacks?
Question 154HardGo 1.22 to 1.25
Every non-leaf function begins with a stack check: it compares SP with g.stackguard0. If the frame will not fit, the function calls runtime.morestack. That function allocates a new stack twice the size, copies the old stack into it, and fixes every pointer into the stack using the compiler's stack maps. The function then restarts.
- Segmented stacks (before Go 1.3) linked new chunks onto the old stack. This caused the hot split problem: a call right at a segment boundary inside a loop allocated and freed a segment on every iteration.
- Contiguous or copying stacks (Go 1.3+) make growth cost amortized O(1). The trade-off is that the runtime must know where every pointer into the stack is. This is one reason Go does not let you keep pointers to stack memory from C.
During GC, stacks shrink to half their size when less than a quarter is used. On 64-bit systems the maximum stack is 1 GB. Going past it gives fatal error: stack overflow ("goroutine stack exceeds 1000000000-byte limit"), and this cannot be recovered.
Gotcha: because stacks move, &local addresses can change over time. Never store uintptr copies of them.
More on Goroutines & the Scheduler
- Q152What is preemption in Go? Explain cooperative vs. asynchronous preemption (Go 1.14).
- Q153What does this program do on Go 1.13 vs Go 1.14+?
- Q155What is GOMAXPROCS, what is its default, and what changed in Go 1.25 for containers?
- Q156Should you set GOMAXPROCS higher than the number of CPUs for I/O-bound workloads?
- Q157What happens to the scheduler when a goroutine makes a blocking syscall?
- Q158What is the netpoller and how does it make network I/O look blocking yet scale?