sequenceDiagram
participant S as Scheduler
participant A as Goroutine A
participant B as Goroutine B
S->>A: Schedule on P0Note over A: Executing...A->>A: Channel send (yield point)A->>S: Yield controlS->>B: Schedule on P0Note over B: Executing...B->>B: runtime.Gosched()B->>S: Yield controlS->>A: Re-schedule on P0Note over A: Resumes execution</code></pre></figure>
Async Preemption
Since Go 1.14, sysmon detects goroutines running longer than ~10ms and forces preemption via SIGURG.
sequenceDiagram
participant SM as sysmon
participant G as Goroutine (tight loop)
participant S as Scheduler
G->>G: Running for >10ms (no yield points)SM->>SM: Detects long-running GSM->>G: Send SIGURGNote over G: Signal handler fires, saves stateG->>S: SuspendedS->>S: Schedule next goroutineS->>G: Re-schedule eventually</code></pre></figure>
Choosing the Right Pattern
graph LR
A{"Need\nconcurrency?"}
A -->|No| B["for {} loop\n(direct execution)"]
A -->|Yes| C{"Bounded\nwork?"}
C -->|Yes| D["Worker Pool\n(fixed goroutines)"]
C -->|No| E{"I/O or\nCPU bound?"}
E -->|I/O| F["Goroutine per task\n(scheduler handles blocking)"]
E -->|CPU| G["select {} loop\n(controlled yielding)"]
For most apps, use goroutines everywhere. For the 5% of performance-critical code, choose based on your scheduler profile.