What is the GPM model in Go?
GMP Model
The GMP model is the cornerstone of Go’s runtime scheduler.
G (Goroutine):
- Represents a goroutine, which is a lightweight thread of execution.
 - Contains the stack, the instruction pointer, and other information important for scheduling.
 - Many Gs can exist at the same time.
 
P (Processor):
- Represents a logical processor, which can be thought of as a context for scheduling.
 - Acts as a local scheduler, managing a queue of runnable goroutines.
 - The number of Ps is typically equal to 
GOMAXPROCS, which by default is the number of CPU cores available. 
M (Machine):
- Represents an OS thread.
 - The Go runtime manages a pool of Ms.
 - Ms execute the code of runnable goroutines.
 
    graph TD
	    G[Goroutine] --> P[Processor]
	    P --> M[Machine]
	    M --> OS[Operating System]
	    
	    subgraph "GMP Model"
	    G
	    P
	    M
	    end
	    
	    OS
Goroutine Scheduling
    flowchart TD
	    GRQ[Global Run Queue] --> |schedule| P1[Processor P1]
	    GRQ --> |schedule| P2[Processor P2]
	    P1 --> |run| G1[Goroutine 1]
	    P1 --> |run| G2[Goroutine 2]
	    P2 --> |run| G3[Goroutine 3]
	    P2 --> |run| G4[Goroutine 4]
	    P1 -.-> |work stealing| P2
	    P2 -.-> |work stealing| P1
	    M1[Machine M1] --> P1
	    M2[Machine M2] --> P2
	    G5[New Goroutine] -.-> |enqueue| GRQ
	    G6[Blocked Goroutine] -.-> |wake up| GRQ
Scheduler Data Structures
Global Run Queue (GRQ)
- Holds runnable goroutines not assigned to any P
 - Represented by 
sched.runqin the runtime - Used when local run queues are full or during work stealing
 - Implemented as a lock-free ring buffer
 
Local Run Queue (LRQ)
- Each P has its own LRQ
 - Holds goroutines assigned to that P
 - Implemented as a circular queue with a fixed size of 256
 - When full, half of the goroutines are moved to the GRQ
 
Idle M List
- Tracks idle OS threads
 - Managed by the scheduler
 - Used to quickly find an M when a goroutine becomes runnable
 
Idle P List
- Maintains idle P structures
 - Used when allocating P to new M
 - Helps in quick P acquisition for waiting Ms
 
Scheduling Algorithm
When a new goroutine is created:
- If there’s space in the current P’s local run queue, add it there
 - Otherwise, add it to the global run queue
 
When a P needs to find a goroutine to run:
- Check its local run queue first
 - If empty, check the global run queue
 - If still empty, try to steal work from other Ps
 
When a goroutine blocks (e.g., on I/O or channel operations):
- The current M will detach from its P
 - Another M will pick up the P and continue running goroutines
 
When a goroutine unblocks:
- It’s placed back on a run queue (local or global)
 - If there’s an idle P, it may be scheduled immediately
 
Advanced Scheduling Concepts
Preemption
Go uses a combination of cooperative and preemptive scheduling:
Cooperative Scheduling
- Goroutines yield control at certain points (e.g., function calls, channel operations)
 - Implemented through checks in the compiler-generated code
 
Preemptive Scheduling
- Introduced in Go 1.14 for long-running goroutines
 - Uses asynchronous preemption via signals (SIGURG on Unix systems)
 - Allows interruption of CPU-bound goroutines
 
Work Stealing
Work stealing is a technique used by the Go scheduler to balance load across processors:
- When a P’s local run queue is empty, it attempts to steal work from other Ps
 - The stealing process is randomized to avoid contention
 - If stealing fails, the P checks the global run queue and network poller
 
Implementation details:
- Uses a random starting point to avoid always stealing from the same P
 - Steals half of the victim’s local run queue to minimize future stealing
 
Syscall Handling
When a goroutine makes a syscall, special handling is required to ensure efficient use of resources:
- The M running the goroutine enters syscall mode
 - The P is detached from the M to allow other goroutines to run
 - If there are no idle Ms, a new M may be created to run the P
 - When the syscall completes, the goroutine is rescheduled
 
Spinning Threads
The Go scheduler uses spinning threads to reduce latency:
- Some Ms may spin instead of going to sleep immediately
 - Spinning Ms check for new work frequently
 - This helps in quickly responding to newly runnable goroutines
 - The number of spinning Ms is limited to avoid wasting CPU
 
Spinning states:
- Spinning looking for work
 - Spinning waiting for GRQ lock
 
Runtime Hooks and Tracing
Go provides several hooks and tracing capabilities for advanced scheduling analysis:
Runtime Tracing
- Enabled with 
runtime/tracepackage - Provides detailed information about goroutine scheduling, GC, and more
 
- Enabled with 
 Scheduler Tracing
- Enabled with 
GODEBUG=schedtrace=Xenvironment variable - Prints scheduler state every X milliseconds
 
- Enabled with 
 Execution Tracing
- Uses 
runtime.Trace()andruntime.StopTrace()functions - Allows for custom tracing points in code
 
- Uses 
 
Example of using runtime tracing:
package main
import (
    "os"
    "runtime/trace"
)
func main() {
    // Create a trace file
    f, err := os.Create("trace.out")
    if err != nil {
        panic(err)
    }
    defer f.Close()
    // Start tracing
    err = trace.Start(f)
    if err != nil {
        panic(err)
    }
    defer trace.Stop()
    // Your program logic here
    for i := 0; i < 10; i++ {
        go func() {
            // Some work
        }()
    }
    // Wait for goroutines to finish
    // ...
}
To analyze the trace:
go tool trace trace.out