Skip to main content

Scheduler

Overview

The Go scheduler is a cooperative + preemptive runtime scheduler that balances goroutines across OS threads and cores efficiently, hiding most complexity from the developer. The Go scheduler is part of the runtime. Its job is to map lightweight goroutines (G) onto OS threads (M) that run on CPU cores (P).

This model is often called G–P–M:

  • GGoroutine (function + stack + metadata)
  • PProcessor (a resource that holds the run queue of goroutines; limited by GOMAXPROCS)
  • MMachine (OS thread that executes goroutines)

G–P–M Model

  1. A goroutine is started with go f().
  2. The runtime creates a G and puts it on a run queue attached to a P.
  3. An M (OS thread) picks a G from its P's queue and runs it.
  4. If the goroutine blocks (e.g., on syscalls, channel ops, locks), the scheduler parks it and runs another goroutine.

Work Stealing

Each P has its own local run queue.

  • If a P runs out of goroutines, it can steal work from another P's queue.
  • Keeps the system balanced across cores.

Concurrency & Parallelism

  • Concurrency: Scheduler switches goroutines on fewer threads than tasks.
  • Parallelism: If GOMAXPROCS > 1, multiple OS threads execute goroutines truly in parallel.

Example:

func main() {
runtime.GOMAXPROCS(4) // allow 4 threads
for i := 0; i < 10; i++ {
go fmt.Println(i)
}
time.Sleep(time.Second) // let goroutines finish
}

Here up to 4 goroutines can run in parallel.

Preemption

Originally, goroutines yielded only at "safe points" (like channel ops, function calls). Since Go 1.14: asynchronous preemption — the scheduler can stop a goroutine almost anywhere to prevent long hogging of the CPU.

Goroutines vs OS threads

  • Goroutines are much lighter: a few KB stack vs MB for threads.
  • Go runtime multiplexes thousands of goroutines over a small number of threads.
  • You can easily spawn millions of goroutines without crashing (if you don't exhaust memory).

Key Features

  • Non-blocking I/O integration: Blocking syscalls are handed to dedicated threads so they don't block all goroutines.
  • Scheduling fairness: Goroutines are given a fair chance (with some tuning to avoid starvation).
  • Garbage collector cooperation: The scheduler works closely with the GC (stop-the-world phases, safe points).