Go的线程实现模型,有三个核心的元素M、P、G,它们共同支撑起了这个线程模型的框架。其中,G是goroutine的缩写,通常称为“协程”。关于协程、线程和进程三者的异同,可以参照“进程、线程和协程的区别”。
每一个Goroutine在程序运行期间,都会对应分配一个g结构体对象。g中存储着Goroutine的运行堆栈、状态以及任务函数,g结构的定义位于src/runtime/runtime2.go文件中。
g对象可以重复使用,当一个goroutine退出时,g对象会被放到一个空闲的g对象池中以用于后续的goroutine 的使用,以减少内存分配开销。
g字段非常的多,我们这里分段来理解:
typegstruct{ //Stackparameters. //stackdescribestheactualstackmemory:[stack.lo,stack.hi). //stackguard0isthestackpointercomparedintheGostackgrowthprologue. //Itisstack.lo+StackGuardnormally,butcanbeStackPreempttotriggerapreemption. //stackguard1isthestackpointercomparedintheCstackgrowthprologue. //Itisstack.lo+StackGuardong0andgsignalstacks. //Itis~0onothergoroutinestacks,totriggeracalltomorestackc(andcrash). stackstack//offsetknowntoruntime/cgo
//检查栈空间是否足够的值,低于这个值会扩张,stackguard0供Go代码使用 stackguard0uintptr//offsetknowntoliblink
//检查栈空间是否足够的值,低于这个值会扩张,stackguard1供C代码使用 stackguard1uintptr//offsetknowntoliblink }
stack描述了当前goroutine的栈内存范围[stack.lo,stack.hi),其中stack的数据结构:
//StackdescribesaGoexecutionstack. //Theboundsofthestackareexactly[lo,hi), //withnoimplicitdatastructuresoneitherside. //描述goroutine执行栈 //栈边界为[lo,hi),左包含右不包含,即lo≤stack<hi //两边都没有隐含的数据结构。 typestackstruct{ louintptr//该协程拥有的栈低位 hiuintptr//该协程拥有的栈高位 }
stackguard0和stackguard1均是一个栈指针,用于扩容场景,前者用于Gostack,后者用于Cstack。
如果stackguard0字段被设置成StackPreempt,意味着当前Goroutine发出了抢占请求。
在g结构体中的stackguard0字段是出现爆栈前的警戒线。stackguard0的偏移量是16个字节,与当前的真实SP(stackpointer)和爆栈警戒线(stack.lo+StackGuard)比较,如果超出警戒线则表示需要进行栈扩容。先调用runtime·morestack_noctxt()进行栈扩容,然后又跳回到函数的开始位置,此时此刻函数的栈已经调整了。然后再进行一次栈大小的检测,如果依然不足则继续扩容,直到栈足够大为止。
typegstruct{ preemptbool//preemptionsignal,duplicatesstackguard0=stackpreempt preemptStopbool//transitionto_Gpreemptedonpreemption;otherwise,justdeschedule preemptShrinkbool//shrinkstackatsynchronoussafepoint }
preempt抢占标记,其值为true执行stackguard0=stackpreempt。
preemptStop将抢占标记修改为_Gpreedmpted,如果修改失败则取消。
preemptShrink在同步安全点收缩栈。
typegstruct{
_panic*_panic//innermostpanic-offsetknowntoliblink _defer*_defer//innermostdefer}
_panic当前Goroutine中的panic。
_defer当前Goroutine中的defer。
typegstruct{
m*m//currentm;offsetknowntoarmliblink schedgobuf goidint64}
m当前Goroutine绑定的M。
sched存储当前Goroutine调度相关的数据,上下方切换时会把当前信息保存到这里,用的时候再取出来。
goid当前Goroutine的唯一标识,对开发者不可见,一般不使用此字段,Go开发团队未向外开放访问此字段。
gobuf结构体定义:
typegobufstruct{ //Theoffsetsofsp,pc,andgareknownto(hard-codedin)libmach. //寄存器sp,pc和g的偏移量,硬编码在libmach // //ctxtisunusualwithrespecttoGC:itmaybea //heap-allocatedfuncval,soGCneedstotrackit,butit //needstobesetandclearedfromassembly,whereits //difficulttohavewritebarriers.However,ctxtisreallya //saved,liveregister,andweonlyeverexchangeitbetween //therealregisterandthegobuf.Hence,wetreatitasa //rootduringstackscanning,whichmeansassemblythatsaves //andrestoresitdoesntneedwritebarriers.Itsstill //typedasapointersothatanyotherwritesfromGoget //writebarriers. spuintptr pcuintptr gguintptr ctxtunsafe.Pointer retsys.Uintreg lruintptr bpuintptr//forGOEXPERIMENT=framepointer }
sp栈指针位置。 pc程序计数器,运行到的程序位置。 ctxt不常见,可能是一个分配在heap的函数变量,因此GC需要追踪它,不过它有可能需要设置并进行清除,在有写屏障的时候有些困难。重点了解一下writebarriers。 g当前gobuf的Goroutine。 ret系统调用的结果。调度器在将G由一种状态变更为另一种状态时,需要将上下文信息保存到这个gobuf结构体,当再次运行G 的时候,再从这个结构体中读取出来,它主要用来暂存上下文信息。其中的栈指针sp和程序计数器pc会用来存储或者恢复寄存器中的值,设置即将执行的代码。
Goroutine的状态有以下几种:
状态描述_Gidle0刚刚被分配并且还没有被初始化_Grunnable1没有执行代码,没有栈的所有权,存储在运行队列中_Grunning2 可以执行代码,拥有栈的所有权,被赋予了内核线程M和处理器P_Gsyscall3 正在执行系统调用,没有执行用户代码,拥有栈的所有权,被赋予了内核线程M但是不在运行队列上_Gwaiting4 由于运行时而被阻塞,没有执行用户代码并且不在运行队列上,但是可能存在于Channel 的等待队列上。若需要时执行ready()唤醒。_Gmoribund_unused5当前此状态未使用,但硬编码在了gdb 脚本里,可以不用关注_Gdead6 没有被使用,可能刚刚退出,或在一个freelist;也或者刚刚被初始化;没有执行代码,可能有分配的栈也可能没有;G和分配的栈(如果已分配过栈)归刚刚退出G的M所有或从free list中获取_Genqueue_unused7目前未使用,不用理会_Gcopystack8 栈正在被拷贝,没有执行代码,不在运行队列上_Gpreempted9由于抢占而被阻塞,没有执行用户代码并且不在运行队列上,等待唤醒_Gscan10 GC正在扫描栈空间,没有执行代码,可以与其他状态同时存在
需要注意的是对于_Gmoribund_unused状态并未使用,但在gdb脚本中存在;而对于_Genqueue_unused 状态目前也未使用,不需要关心。
_Gscan与上面除了_Grunning状态以外的其它状态相组合,表示GC正在扫描栈。Goroutine不会执行用户代码,且栈由设置了 _Gscan位的Goroutine所有。
状态描述_Gscanrunnable=_Gscan+_Grunnable//0x1001_Gscanrunning=_Gscan+ _Grunning//0x1002_Gscansyscall=_Gscan+_Gsyscall// 0x1003_Gscanwaiting=_Gscan+_Gwaiting//0x1004_Gscanpreempted=_Gscan+ _Gpreempted//0x1009
可以看到除了上面提到的两个未使用的状态外一共有14种状态值。许多状态之间是可以进行改变的。如下图所示:
typegstrcut{ syscallspuintptr//ifstatus==Gsyscall,syscallsp=sched.sptouseduringgc syscallpcuintptr//ifstatus==Gsyscall,syscallpc=sched.pctouseduringgc stktopspuintptr//expectedspattopofstack,tocheckintraceback paramunsafe.Pointer//passedparameteronwakeup atomicstatusuint32 stackLockuint32//sigprof/scanglock;TODO:foldintoatomicstatus }
atomicstatus当前G的状态,上面介绍过G的几种状态值。
syscallsp如果G的状态为Gsyscall,那么值为sched.sp主要用于GC期间。
syscallpc如果G的状态为GSyscall,那么值为sched.pc主要用于GC期间。由此可见这两个字段通常一起使用。
stktopsp用于回源跟踪。
param唤醒G时传入的参数,例如调用ready()。
stackLock栈锁。
typegstruct{
waitsinceint64//approxtimewhenthegbecomeblocked waitreasonwaitReason//ifstatus==Gwaiting}
waitsinceG阻塞时长。
waitreason阻塞原因。
typegstruct{
//asyncSafePointissetifgisstoppedatanasynchronous //safepoint.Thismeansthereareframesonthestack //withoutprecisepointerinformation. asyncSafePointbool paniconfaultbool//panic(insteadofcrash)onunexpectedfaultaddress gcscandonebool//ghasscannedstack;protectedby_Gscanbitinstatus throwsplitbool//mustnotsplitstack}
asyncSafePoint异步安全点;如果g在异步安全点停止则设置为true,表示在栈上没有精确的指针信息。
paniconfault地址异常引起的panic(代替了崩溃)。
gcscandoneg扫描完了栈,受状态_Gscan位保护。
throwsplit不允许拆分stack。
typegstruct{
//activeStackChansindicatesthatthereareunlockedchannels //pointingintothisgoroutinesstack.Iftrue,stack //copyingneedstoacquirechannellockstoprotectthese //areasofthestack. activeStackChansbool //parkingOnChanindicatesthatthegoroutineisaboutto //parkonachansendorchanrecv.Usedtosignalanunsafepoint //forstackshrinking.Itsabooleanvalue,butisupdatedatomically. parkingOnChanuint8}
activeStackChans表示是否有未加锁定的channel指向到了g栈,如果为true,那么对栈的复制需要channal锁来保护这些区域。
parkingOnChan表示g是放在chansend还是chanrecv。用于栈的收缩,是一个布尔值,但是原子性更新。
typegstruct{
raceignoreint8//ignoreracedetectionevents sysblocktracedbool//StartTracehasemittedEvGoInSyscallaboutthisgoroutine sysexitticksint64//cputickswhensyscallhasreturned(fortracing) tracesequint64//traceeventsequencer tracelastppuintptr//lastPemittedaneventforthisgoroutine lockedmmuintptr siguint32 writebuf[]byte sigcode0uintptr sigcode1uintptr sigpcuintptr gopcuintptr//pcofgostatementthatcreatedthisgoroutine ancestors*[]ancestorInfo//ancestorinformationgoroutine(s)thatcreatedthisgoroutine(onlyusedifdebug.tracebackancestors) startpcuintptr//pcofgoroutinefunction racectxuintptr waiting*sudog//sudogstructuresthisgiswaitingon(thathaveavalidelemptr);inlockorder cgoCtxt[]uintptr//cgotracebackcontext labelsunsafe.Pointer//profilerlabels timer*timer//cachedtimerfortime.Sleep selectDoneuint32//areweparticipatinginaselectanddidsomeonewintherace?}
gopc创建当前G的pc。
startpcgofunc的pc。
timer通过time.Sleep缓存timer。
typegstruct{
//Per-GGCstate //gcAssistBytesisthisGsGCassistcreditintermsof //bytesallocated.Ifthisispositive,thentheGhascredit //toallocategcAssistBytesbyteswithoutassisting.Ifthis //isnegative,thentheGmustcorrectthisbyperforming //scanwork.Wetrackthisinbytestomakeitfasttoupdate //andcheckfordebtinthemallochotpath.Theassistratio //determineshowthiscorrespondstoscanworkdebt. gcAssistBytesint64}
gcAssistBytes与GC相关。