先来看看goroutine调度器相关重要结构体,位于Go源代码下runtime/runtime2.go。
因其成员变量极多,细节极其复杂,所以只看与调度器相关的成员变量。
stack:记录goroutine使用的栈信息,包括栈顶和栈底的位置信息。
// Stack describes a Go execution stack.// The bounds of the stack are exactly [lo, hi),// with no implicit data structures on either side.//用于记录goroutine使用的栈的起始和结束位置type stack struct {lo uintptr // 栈顶,指向内存低地址hi uintptr // 栈底,指向内存高地址}
gobuf:保存goroutine的调度信息,主要是CPU几个寄存器的值。
type gobuf struct {// The offsets of sp, pc, and g are known to (hard-coded in) libmach.//// ctxt is unusual with respect to GC: it may be a// heap-allocated funcval, so GC needs to track it, but it// needs to be set and cleared from assembly, where it's// difficult to have write barriers. However, ctxt is really a// saved, live register, and we only ever exchange it between// the real register and the gobuf. Hence, we treat it as a// root during stack scanning, which means assembly that saves// and restores it doesn't need write barriers. It's still// typed as a pointer so that any other writes from Go get// write barriers.sp uintptr // 保存CPU的rsp寄存器的值pc uintptr // 保存CPU的rip寄存器的值g guintptr // 记录当前这个gobuf对象属于哪个goroutinectxt unsafe.Pointer// 保存系统调用的返回值,因为从系统调用返回之后如果p被其它工作线程抢占,// 则这个goroutine会被放入全局运行队列被其它工作线程调度,其它线程需要知道系统调用的返回值。ret sys.Uintreglr uintptr// 保存CPU的rip寄存器的值bp uintptr // for GOEXPERIMENT=framepointer}
g:用于代表一个goroutine,保存了goroutine的所有信息,包括栈。
// 前文所说的g结构体,它代表了一个goroutinetype g struct {// Stack parameters.// stack describes the actual stack memory: [stack.lo, stack.hi).// stackguard0 is the stack pointer compared in the Go stack growth prologue.// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.// stackguard1 is the stack pointer compared in the C stack growth prologue.// It is stack.lo+StackGuard on g0 and gsignal stacks.// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).// 记录该goroutine使用的栈stack stack // offset known to runtime/cgo// 下面两个成员用于栈溢出检查,实现栈的自动伸缩,抢占调度也会用到stackguard0stackguard0 uintptr // offset known to liblinkstackguard1 uintptr // offset known to liblink......// 此goroutine正在被哪个工作线程执行m *m // current m; offset known to arm liblink// 保存调度信息,主要是几个寄存器的值sched gobuf......// schedlink字段指向全局运行队列中的下一个g,//所有位于全局运行队列中的g形成一个链表schedlink guintptr......// 抢占调度标志,如果需要抢占调度,设置preempt为truepreempt bool // preemption signal, duplicates stackguard0 = stackpreempt......}
m:用于代表工作线程,保存自身使用栈信息和当前正在运行goroutine及m绑定的p。
type m struct {// g0主要用来记录工作线程使用的栈信息,在执行调度代码时需要使用这个栈// 执行用户goroutine代码时,使用用户goroutine自己的栈,调度时会发生栈的切换g0 *g // goroutine with scheduling stack// 通过TLS实现m结构体对象与工作线程之间的绑定tls [6]uintptr // thread-local storage (for x86 extern register)mstartfn func()// 指向工作线程正在运行的goroutine的g结构体对象curg *g // current running goroutine// 记录与当前工作线程绑定的p结构体对象p puintptr // attached p for executing go code (nil if not executing go code)nextp puintptroldp puintptr // the p that was attached before executing a syscall// spinning状态:表示当前工作线程正在试图从其它工作线程的本地运行队列偷取goroutinespinning bool // m is out of work and is actively looking for workblocked bool // m is blocked on a note// 没有goroutine需要运行时,工作线程睡眠在这个park成员上,// 其它线程通过这个park唤醒该工作线程park note// 记录所有工作线程的一个链表alllink *m // on allmschedlink muintptr// Linux平台thread的值就是操作系统线程IDthread uintptr // thread handlefreelink *m // on sched.freem......}
p:用于保存工作线程执行Go代码所必需的资源,如goroutine运行队列以及内存分配用到的缓存等信息。
type p struct {lock mutexstatus uint32 // one of pidle/prunning/...link puintptrschedtick uint32 // incremented on every scheduler callsyscalltick uint32 // incremented on every system callsysmontick sysmontick // last tick observed by sysmonm muintptr // back-link to associated m (nil if idle)......// Queue of runnable goroutines. Accessed without lock.//本地goroutine运行队列runqhead uint32 // 队列头runqtail uint32 // 队列尾runq [256]guintptr //使用数组实现的循环队列// runnext, if non-nil, is a runnable G that was ready'd by// the current G and should be run next instead of what's in// runq if there's time remaining in the running G's time// slice. It will inherit the time left in the current time// slice. If a set of goroutines is locked in a// communicate-and-wait pattern, this schedules that set as a// unit and eliminates the (potentially large) scheduling// latency that otherwise arises from adding the ready'd// goroutines to the end of the run queue.runnext guintptr// Available G's (status == Gdead)gFree struct {gListn int32}......}
schedt:保存调度器的状态信息以及goroutine的全局运行队列。
type schedt struct {// accessed atomically. keep at top to ensure alignment on 32-bit systems.goidgen uint64lastpoll uint64lock mutex// When increasing nmidle, nmidlelocked, nmsys, or nmfreed, be// sure to call checkdead().// 由空闲的工作线程组成链表midle muintptr // idle m's waiting for work// 空闲的工作线程的数量nmidle int32 // number of idle m's waiting for worknmidlelocked int32 // number of locked m's waiting for workmnext int64 // number of m's that have been created and next M ID// 最多只能创建maxmcount个工作线程maxmcount int32 // maximum number of m's allowed (or die)nmsys int32 // number of system m's not counted for deadlocknmfreed int64 // cumulative number of freed m'sngsys uint32 // number of system goroutines; updated atomically// 由空闲的p结构体对象组成的链表pidle puintptr // idle p's// 空闲的p结构体对象的数量npidle uint32nmspinning uint32 // See "Worker thread parking/unparking" comment in proc.go.// Global runnable queue.// goroutine全局运行队列runq gQueuerunqsize int32......// Global cache of dead G's.// gFree是所有已经退出的goroutine对应的g结构体对象组成的链表// 用于缓存g结构体对象,避免每次创建goroutine时都重新分配内存gFree struct {lock mutexstack gList // Gs with stacksnoStack gList // Gs without stacksn int32}......}
再来看看goroutine调度器相关重要全局变量。
allgs []*g // 保存所有的gallm *m // 所有的m构成的一个链表,包括下面的m0allp []*p // 保存所有的p,len(allp) == gomaxprocsncpu int32 // 系统中cpu核的数量,程序启动时由runtime代码初始化gomaxprocs int32 // p的最大值,默认等于ncpu,但可以通过GOMAXPROCS修改sched schedt // 调度器结构体对象,记录了调度器的工作状态m0 m // 代表进程的主线程g0 g // m0的g0,也就是m0.g0 = &g0
上述变量在程序初始化时,全部会被初始化为0值,指针会被初始化为nil指针,切片会被初始化为nil切片,int被初始化为数字0,结构体的所有成员变量按其数据类型初始化为自身类型的0值,所以在程序刚启动时,allgs、allm,allp都不包含任何g、m、p。
以上仅为个人观点,不一定准确,能帮到各位那是最好的。
好啦,到这里本文就结束了,喜欢的话就来个三连击吧。
扫码关注公众号,获取更多优质内容。