這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
調度器就是將goroutine分配到背景工作執行緒中運行
涉及3種類型的對象:
G - goroutine
M - 背景工作執行緒即os線程
P - 處理器,一種用來運行go代碼的抽象資源,最大數目不能超過GOMAXPROCS,在運行go代碼時需要關聯一個M
全域的運行隊列:
G *runtime·sched.runqhead;G *runtime·sched.runqtail;int runtime·sched.runqsize;
P結構的主要成員:包含一個本地運行隊列
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函數主要部分代碼
// 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 while to ensure fairness. // Otherwise two goroutines can completely occupy the local runqueue // by constantly respawning each other. tick = g->m->p->schedtick; // This is a fancy way to say tick%61==0, // it uses 2 MUL instructions instead of a single DIV and so is faster on modern 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);}
調度器在超過一定間隔時間的情況下,為了公平原則,首先會從全域的運行隊列擷取G
從本地的運行隊列中擷取G
等待新的G進入運行隊列
globrunqget從全域運行隊列擷取G,同時它還會將一定數量的G轉移到P的本地運行隊列中.
runqget從本地運行隊列擷取G,本地運行隊列的實現是無鎖的:
// 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阻塞等待可啟動並執行G
- 檢查本地運行隊列
- 檢查全域運行隊列
- 以non-blocking的模式poll network
- 檢查其它P的本地運行隊列
如果最後依舊無法在系統內擷取到G,那麼就以blocking的模式poll network
// Finds a runnable goroutine to execute.
// Tries to steal from other P's, 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 but the program parallelism is 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;
}