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
- Using go keyword
- GC
- System calls
- 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.