Runtime

M wraps system threads. P serves as execution context for G. Each P maintains its own queue, avoiding global queue access and reducing lock granularity.

Initialization

Parse command line arguments. Initialize m0 and start a system thread linked to m0.

Initialize stack space. Create memory allocator and garbage collection goroutine.

Create P objects based on parameters, initially matching CPU count.

The first goroutine runs runtime.Main(). Start the SYSMON monitoring thread to watch garbage collection and goroutine scheduling.

Execute init functions in the main package first. The compiler generates these function contents.

When main ends, call exit(0).

Goroutine Creation

Each new G gets only 4KB of stack space. One gigabyte can hold up to 250,000 G objects.

runtime.newproc():

  • Calculate stack size needed
  • Get pointer to current M’s P, then fetch G object from P’s free list (this is where reuse happens!)
  • If P has no free objects, get from global queue
  • If still nothing, initialize a 2KB function stack (free objects need cleaning first; lazy allocation design)
  • Place G on free P, link with free M, enter running state Grunning

Goroutine State Transitions

This just moves G to different places.

During system calls - call gopark() to block the function G, put it in specific blocking queues

To resume - call goready() to wake from blocking queue, return to ready queue

When done executing - take current G object, change status to Gdead, use gfput to add G to P’s free queue. If expanded, return space, keep only initial function stack.

M

When G makes a system call, SYSMON unbinds P. Find new M to execute startM.

M also has a global free queue (thread pool). If nothing available, call newm().

Allocate thread stack for new thread. Use system calls to create kernel-level thread. Put in M.

Start scheduling logic. Run scheduler in loop until getting G. Execute, then recycle G. Repeat endlessly.

Scheduler

Check GC and STW conditions. Ensure no scheduling during STW.

Get and execute G.

When getting G, check if we’ve taken many times from P’s local queue. If so, acquire global lock to get G from global queue. If we can’t get any, start work stealing.

Conditions That Trigger Scheduling

  1. Using go keyword
  2. GC
  3. System calls
  4. Synchronization and mutex operations

Work Stealing

Steal half the G objects from another M’s P and put them in your queue. P determined by random algorithm. Try 4 times.

If failed, SYSMON stops current M from sleeping. Long system calls also stop M execution, cancel spinning state, put M in idle queue, park M’s system thread, unbind from P.

Stack Growth

First take from stack cache mcache. If not enough, allocate from heap mheap. If still not enough, request from system.

New stack doubles old stack size. Check if greater than max stack allocation value. Only running G can do stack expansion copystack.

Stack Release

shrinkstack shrinks through stack copying.

System Calls

User data -> registers <- kernel execution results

Go wraps system calls. Usually syscall saves state but doesn’t give up P. If system call takes too long, SYSMON separates P and M.

System calls actively give up P. After call ends, try to match original P first.

SYSMON

This monitoring thread can run directly on M without P.