This is a creation in Article, where the information may have evolved or changed.
The scheduler is the assignment of goroutine to a worker thread to run
There are 3 types of objects involved:
G-goroutine
M-worker thread i.e. OS thread
P-processor, an abstract resource used to run go code, the maximum number cannot exceed gomaxprocs, you need to associate a m when running the go Code
Global Run Queue:
G *runtime·sched.runqhead;G *runtime·sched.runqtail;int runtime·sched.runqsize;
Primary member of the P structure: contains a local run queue
struct P{ uint32status;// one of Pidle/Prunning/... uint32schedtick;// incremented on every scheduler call M*m;// back-link to associated M (nil if idle) // Queue of runnable goroutines. uint32runqhead; uint32runqtail; G*runq[256];};
Schedule function Main part code
One round of Scheduler:find a runnable goroutine and execute it.//never returns.static voidschedule (void) {G *gp; UInt32 tick; ..... Top: ... gp = nil; Check the global runnable queue once in a and to ensure fairness. Otherwise Goroutines can completely occupy the local runqueue//by constantly respawning per other. Tick = g->m->p->schedtick; This is a fancy-to-say tick%61==0,//It uses 2 MUL instructions instead of a single DIV and so are faster on mod Ern processors. if (tick-((UInt64) tick*0x4325c53fu) >>36) *61 = = 0 && runtime sched.runqsize > 0) {Runtime Lock (& Runtime Sched.lock); GP = Globrunqget (g->m->p, 1); Runtime unlock (&runtime sched.lock); if (GP) resetspinning (); } if (gp = = nil) {GP = Runqget (g->m->p); if (GP && g->m->spinning) Runtime throw ("schedule:spinning with local work"); } if (gp = = nil) {GP = FindruNnable (); Blocks until work is available resetspinning (); } execute (GP);}
In the case of a scheduler exceeding a certain interval, for the sake of fairness, the first is to get G from the global run queue
Get g from the local run queue
Wait for the new G to enter the run queue
Globrunqget gets G from the global run queue, and it also transfers a certain number of G to the local run queue of P.
Runqget get G from the local run queue, the implementation of the local run queue is unlocked:
// Get g from local runnable queue.// Executed only by the owner P.static G*runqget(P *p){ G *gp; uint32 t, h; for(;;) { h = runtime·atomicload(&p->runqhead); // load-acquire, synchronize with other consumers t = p->runqtail; if(t == h) return nil; gp = p->runq[h%nelem(p->runq)]; if(runtime·cas(&p->runqhead, h, h+1)) // cas-release, commits consume return gp; }}
Findrunnable blocking waiting for a running G
- Check the local run queue
- Check the global run queue
- Poll Network in non-blocking mode
- Check the local run queue of other p
If G is still not available in the system at the end, then poll the network in blocking mode
Finds a runnable goroutine to execute.
Tries to steal from the other P ' s, the get G from global queue, poll network.
Static G
Findrunnable (void)
{
G GP;
P *p;
Int32 i;
Top
if (runtime sched.gcwaiting) {
GCSTOPM ();
Goto top;
}
if (runtime fingwait && runtime Fingwake && (gp = Runtime wakefing ()) = nil)
Runtime ready (GP);
Local RUNQ
GP = Runqget (g->m->p);
if (GP)
return GP;
Global RUNQ
if (runtime Sched.runqsize) {
Runtime Lock (&runtime Sched.lock);
GP = Globrunqget (g->m->p, 0);
Runtime unlock (&runtime sched.lock);
if (GP)
return GP;
}
Poll Network
GP = Runtime Netpoll (false); Non-blocking
if (GP) {
Injectglist (Gp->schedlink);
Runtime Casgstatus (GP, gwaiting, grunnable);
return GP;
}
If number of spinning M ' s >= number of busy P ' s, block.
This is necessary to prevent excessive CPU consumption
When Gomaxprocs>>1 and the program parallelism are low.
if (!g->m->spinning && 2 * Runtime Atomicload (&runtime sched.nmspinning) >= Runtime Gomaxprocs- Runtime Atomicload (&runtime Sched.npidle))//Todo:fast Atomic
Goto stop;
if (!g->m->spinning) {
G->m->spinning = true;
Runtime Xadd (&runtime sched.nmspinning, 1);
}
Random steal from other P ' s
for (i = 0; i < 2*runtime gomaxprocs; i++) {
if (runtime sched.gcwaiting)
Goto top;
p = Runtime Allp[runtime fastrand1 ()%runtime Gomaxprocs];
if (p = = g->m->p)
GP = Runqget (p);
Else
GP = Runqsteal (g->m->p, p);
if (GP)
return GP;
}
Stop
Return P and block
Runtime Lock (&runtime Sched.lock);
if (runtime sched.gcwaiting) {
Runtime unlock (&runtime sched.lock);
Goto top;
}
if (runtime Sched.runqsize) {
GP = Globrunqget (g->m->p, 0);
Runtime unlock (&runtime sched.lock);
return GP;
}
p = releasep ();
Pidleput (P);
Runtime unlock (&runtime sched.lock);
if (g->m->spinning) {
G->m->spinning = false;
Runtime Xadd (&runtime sched.nmspinning,-1);
}
Check all runqueues once again
for (i = 0; i < runtime gomaxprocs; i++) {
p = Runtime Allp[i];
if (P && p->runqhead! = p->runqtail) {
Runtime Lock (&runtime Sched.lock);
p = pidleget ();
Runtime unlock (&runtime sched.lock);
if (p) {
Acquirep (P);
Goto top;
}
Break
}
}
Poll Network
if (Runtime xchg64 (&runtime sched.lastpoll, 0)! = 0) {
if (g->m->p)
Runtime throw ("Findrunnable:netpoll with P");
if (g->m->spinning)
Runtime throw ("Findrunnable:netpoll with Spinning");
GP = Runtime Netpoll (true); Block until new work is available
Runtime Atomicstore64 (&runtime Sched.lastpoll, Runtime Nanotime ());
if (GP) {
Runtime Lock (&runtime Sched.lock);
p = pidleget ();
Runtime unlock (&runtime sched.lock);
if (p) {
Acquirep (P);
Injectglist (Gp->schedlink);
Runtime Casgstatus (GP, gwaiting, grunnable);
return GP;
}
Injectglist (GP);
}
}
STOPM ();
Goto top;
}