This is a creation in Article, where the information may have evolved or changed.
Description
Although we have always emphasized that the Golang Scheduler is non-preemptive. One of the biggest drawbacks of non-preemption is the inability to guarantee fairness, and if a G is in a dead loop, other processes may starve. Fortunately, Golang in the 1.4 version of the preemptive scheduling logic, preemptive scheduling is bound to be at some point in g execution is stripped of the CPU, to other co-process.
Realize
Remember what we said earlier about Golang's Sysmon, which periodically wakes up as a system state check, we said earlier how it checks p in the Psyscall state so that p in the system call state can be continued without starvation. In addition to checking the accident, Sysmon also checks p in the prunning state, checking it to avoid a certain g here taking up too much CPU time, and at some point depriving it of its CPU run time.
Static UInt32 Retake (Int64 now) {UInt32 I, S, N; Int64 T; P *p; Pdesc *PD; n = 0; for (i = 0; i < runtime gomaxprocs; i++) {p = runtime allp[i]; if (P==nil) continue; PD = &pdesc[i]; s = p->status; if (s = = Psyscall) {...} else if (s = = prunning) {//Preempt G If it ' s running for more T Han 10ms. t = p->schedtick; if (Pd->schedtick! = t) {Pd->schedtick = t; Pd->schedwhen = Now; Continue } if (Pd->schedwhen + 10*1000*1000 > Now) continue; If the schedule has exceeded 10ms Preemptone (p) Since the last time it occurred; }} return n;} The preemption here just sets the G's preempt to true//only checks the flag bit when G makes a function call//And, in turn, may dispatch, very weak static bool Preemptone (P *p) {M *mp; G *GP; MP = p->m; if (MP = = Nil | | mp = = G->M) return false; GP = mp->curg; if (gp = = Nil | | gp = = MP->G0) return false; Gp->preempt = true; Every call in a go routine checks for stacks overflow by//comparing the current stack pointer to Gp->stackguard 0.//Setting gp->stackguard0 to stackpreempt folds//preemption into the normal stack overflow check. gp->stackguard0 = stackpreempt; return true;}
As we said before in the function call stack detection, now set GP->STACKGUARD0 to Stackpreempt (-1314, very small value), will definitely call once Runtime.morestack, the logic is as follows:
TEXT runtime Morestack (SB), nosplit,$0-0//Cannot grow scheduler stack (M->G0). GET_TLS (CX) movq g (CX), BX movq g_m (BX), BX movq m_g0 (BX), Si cmpq g (CX), Si JNE 2 (PC) I NT $//Cannot grow signal stack (m->gsignal). Movq m_gsignal (BX), Si cmpq g (CX), Si JNE 2 (PC) INT $ $//called from F. Set m->morebuf to F ' s caller. MOVQ 8 (SP), Ax//F ' s caller ' s PC movq AX, (M_MOREBUF+GOBUF_PC) (BX) Leaq (SP), Ax//F's caller ' s SP Movq AX, (M_MOREBUF+GOBUF_SP) (BX) GET_TLS (CX) movq g (CX), Si movq si, (m_morebuf+gobuf_g) (BX)//Se T g->sched to context in F. MOVQ 0 (SP), Ax//F ' s PC movq ax, (g_sched+gobuf_pc) (SI) movq SI, (g_sched+gobuf_g) (SI) Leaq 8 (SP), Ax//F ' s SP movq AX, (g_sched+gobuf_sp) (SI) movq DX, (g_sched+gobuf_ctxt) (SI) movq BP, (G_SCHED+GOBUF_BP) (SI) Call Newstack on M->g0 ' s stack. MOVQ m_g0 (BX), BX MOVQ BX, G (CX) Movq (G_SCHED+GOBUF_SP) (BX), SP call Runtime Newstack (SB) movq $, 0x1003//crash if Newstack Returns RET
Final call to Newstack for stack expansion:
Func Newstack () {thisg: = GETG ()//todo:double Check all GP. Shouldn ' t is GETG (). If Thisg.m.morebuf.g.ptr (). stackguard0 = = stackfork {throw ("stack growth after fork")} if THISG.M.MOREBUF.G . PTR ()! = Thisg.m.curg {print ("runtime:newstack called from g=", THISG.M.MOREBUF.G, "\ n" + "\tm=", THISG.M, "M-> ; curg= ", Thisg.m.curg," m->g0= ", Thisg.m.g0," m->gsignal= ", thisg.m.gsignal," \ n ") Morebuf: = Thisg.m.moreb UF traceback (morebuf.pc, Morebuf.sp, MOREBUF.LR, Morebuf.g.ptr ()) throw ("Runtime:wrong goroutine in Newsta CK ")} GP: = Thisg.m.curg morebuf: = Thisg.m.morebuf thisg.m.morebuf.pc = 0 thisg.m.morebuf.lr = 0 thi SG.M.MOREBUF.SP = 0 thisg.m.morebuf.g = 0 rewindmorestack (&gp.sched)//note:stackguard0 Change Underfoo T, if another thread//is the about-to-try to preempt GP. Read it just once and use this same//value now and below. Preempt: = Atomicloaduintptr (&gp.stackguard0)= = Stackpreempt If preempt {if thisg.m.locks! = 0 | | thisg.m.mallocing! = 0 | | Thisg.m.preemptoff! = "" | | Thisg.m.p.ptr (). Status! = _prunning {//Let the Goroutine keep running for now. Gp->preempt is set and so it'll be preempted next time. gp.stackguard0 = Gp.stack.lo + _stackguard gogo (&gp.sched)//Never return}} ...// Rescheduling if preempt {if GP = = thisg.m.g0 {throw ("Runtime:preempt G0")} if THISG.M.P = = 0 && Thisg.m.locks = = 0 {throw ("Runtime:g is running but P was not")} if Gp.preemptscan {for!castogscanstatus (GP, _gwaiting, _gscanwaiting) {//likely to is racing with the GC as It sees a _gwaiting and does the//stack scan. If So, Gcworkdone'll/be set and gcphasework'll simply//return. } If!gp.gcscanDone {scanstack (gp) Gp.gcscandone = true} Gp.preemptscan = False Gp.preempt = False Casfrom_gscanstatus (GP, _gscanwaiting, _gwaiting) casgstatus (GP, _gwaitin G, _grunning) gp.stackguard0 = Gp.stack.lo + _stackguard gogo (&gp.sched)//Never return }//Act like goroutine called Runtime. Gosched. Casgstatus (GP, _gwaiting, _grunning)//Discard current coprocessor, schedule new coprocessor Execution Gopreempt_m (GP)//Never return}}
Here are two things to note:
- THISG: = GETG (): This is the stack that represents the current execution of the Newstack () function, and also the G0 of the current thread;
- GP: = Thisg.m.curg: This represents the application stack expansion process, and the above THISG is not a thing.
Because although the call to Newstack, but for the stackguard0==stackpreempt, it is not the purpose of the stack expansion, but to initiate a dispatch, so directly into the gopreempt_m, where the current process hangs, and initiate a schedule ().