Demystifying Go's Scheduler: How Goroutines, M, P, and G Work Together

This article explains the inner workings of Go's runtime scheduler, detailing how lightweight goroutines (G), logical processors (P), and OS threads (M) interact, how work stealing operates, and how to trace scheduler behavior using GODEBUG and go tool trace.

360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
Demystifying Go's Scheduler: How Goroutines, M, P, and G Work Together

1. Basics

Go's runtime manages scheduling, garbage collection, and the execution environment for goroutines. The scheduler maps goroutines to OS threads. Each goroutine is represented by a G struct that tracks its stack and state. Logical processors, called P, hold queues of runnable Gs, and OS threads, called M, execute the Gs assigned to a P.

The number of logical processors can be set with runtime.GOMAXPROCS(numLogicalProcessors), after which the runtime handles scheduling automatically.

2. The Dance Between Ms, Ps & Gs

The interaction among Ms, Ps, and Gs is complex. A global run queue exists in the schedt structure, but each P maintains its own local run queue of Gs.

When a new goroutine is created (e.g., via go func()), it is placed on the P's queue. If a M finishes executing a G and its P's queue is empty, it randomly steals half of the runnable Gs from another P.

Blocking system calls are intercepted; the runtime detaches the M from its P and creates a new OS thread if needed. When the system call returns, the goroutine is placed back on a local run queue and the thread may become idle.

Network calls are handled similarly, using Go's integrated network poller.

If the current goroutine blocks, the runtime runs a different goroutine. Typical blocking events include:

Blocking system calls (e.g., opening a file)

Network input

Channel operations

Synchronization primitives from the sync package

3. Scheduler Tracing

Go allows tracing of the runtime scheduler via the GODEBUG environment variable.

$ GODEBUG=scheddetail=1,schedtrace=1000 ./program

Example output:

SCHED 0ms: gomaxprocs=8 idleprocs=7 threads=2 spinningthreads=0 idlethreads=0 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 ...
  P0: status=1 schedtick=0 syscalltick=0 m=0 runqsize=0 gfreecnt=0
  P1: status=0 ...
  M0: p=0 curg=1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 helpgc=0 spinning=false blocked=false lockedg=1
  G1: status=8() m=0 lockedm=0

For most use‑cases you can simplify the command: $ GODEBUG=schedtrace=1000 ./program An advanced tool, go tool trace, provides a UI to explore program execution in detail.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

concurrencySchedulerRuntimeGoroutine
360 Zhihui Cloud Developer
Written by

360 Zhihui Cloud Developer

360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.