Memory Layout
code┌─────────────────────────────────────┐ │ Goroutine Stack │ │ (starts at ~2KB, grows dynamically)│ ├─────────────────────────────────────┤ │ Function Call Frames │ │ ┌─────────────────────────────┐ │ │ │ Frame N: Current Function │ │ │ │ - Local variables │ │ │ │ - Return address │ │ │ │ - Function parameters │ │ │ └─────────────────────────────┘ │ │ ┌─────────────────────────────┐ │ │ │ Frame N-1: Caller │ │ │ └─────────────────────────────┘ │ │ ┌─────────────────────────────┐ │ │ │ Frame N-2: ... │ │ │ └─────────────────────────────┘ │ ├─────────────────────────────────────┤ │ Stack Pointer (SP) │ │ Program Counter (PC) │ │ Goroutine ID │ │ Status (running/waiting) │ └─────────────────────────────────────┘
Goroutine Lifecycle States
code┌──────────┐ │ Created │ (go keyword used) └────┬─────┘ │ ▼ ┌──────────┐ │ Runnable │ (waiting in queue) └────┬─────┘ │ ▼ ┌──────────┐ Blocking I/O │ Running │───────────────┐ └────┬─────┘ │ │ ▼ │ ┌──────────┐ │ │ Waiting │ │ └────┬─────┘ │ │ │ Event Ready │ │◄───────────────────┘ │ │ Work Complete ▼ ┌──────────┐ │ Dead │ └──────────┘
Stack Growth Mechanism
Goroutine stacks are dynamically sized and grow/shrink as needed:
Initial State (2KB Stack)
codeStack Start: 0x1000 Stack End: 0x1800 (2KB later) ┌──────────┐ 0x1000 │ │ │ Stack │ │ │ │ │ ← Stack Pointer (0x1600) │ │ └──────────┘ 0x1800
Stack Overflow Detected
codeFunction prologue checks: if SP - FrameSize < StackGuard { // Trigger stack growth morestack() }
New Stack Allocated (4KB)
codeNew Stack: 0x2000 - 0x3000 ┌──────────┐ 0x2000 │ │ │ │ │ New │ │ Stack │ ← Copy old data here │ (4KB) │ │ │ │ │ └──────────┘ 0x3000 1. Allocate new stack (2x size) 2. Copy existing frames 3. Update all pointers 4. Switch to new stack 5. Continue execution
Key Points:
- Check happens at every function call
- Typically doubles in size (2KB → 4KB → 8KB → ...)
- Old stack is garbage collected
- Maximum stack size: 1GB on 64-bit systems
Was this helpful?