go 进阶 协程相关: 二. 协程MPG在代码中对应结构体详解

  1. 前面了解了golang使用的MPG线程模型,golang是怎么实现MPG的,我们先了解一下对应MPG模型的几个结构体

g 结构体

  1. G是goroutine的缩写,又称为“协程”, 每个 Goroutine 对应一个 g 结构体,g结构源码在 src/runtime/runtime2.go 文件
  2. 通过这个g结构体存储了 Goroutine 的运行堆栈、状态以及任务函数,可重复使用, 当一个 goroutine 退出时,g 对象会被放到一个空闲的 g 对象池中,方便后续的 goroutine 的使用,减少内存分配的开销
  3. 重点字段:

在g结构体中的stackguard0 字段是出现爆栈前的警戒线。stackguard0的偏移量是16个字节,与当前的真实SP(stack pointer)和爆栈警戒线(stack.lo+StackGuard)比较,如果超出警戒线则表示需要进行栈扩容.先调用runtime·morestack_noctxt()进行栈扩容,然后又跳回到函数的开始位置,此时此刻函数的栈已经调整了。然后再进行一次栈大小的检测,如果依然不足则继续扩容,直到栈足够大为止

type 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
	//stackguard0 和 stackguard1 均是一个栈指针,用于扩容场景,
	//前者用于 Go stack ,后者用于C stack
	//如果 stackguard0 字段被设置成 StackPreempt 意味着当前 Goroutine 发出了抢占请求
	stackguard0 uintptr // offset known to liblink
	stackguard1 uintptr // offset known to liblink
	//当前Goroutine 中的panic
	_panic    *_panic // innermost panic - offset known to liblink
	//当前Goroutine 中的defer
	_defer    *_defer // innermost defer
	//当前 Goroutine 绑定的M
	m         *m      // current m; offset known to arm liblink
	//调度器,存储当前 Goroutine 调度相关的数据,
	//上下方切换时会把当前信息保存到这里,用的时候再取出来
	sched     gobuf
	syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc
	syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc
	stktopsp  uintptr // expected sp at top of stack, to check in traceback
	// param is a generic pointer parameter field used to pass
	// values in particular contexts where other storage for the
	// parameter would be difficult to find. It is currently used
	// in three ways:
	// 1. When a channel operation wakes up a blocked goroutine, it sets param to
	//    point to the sudog of the completed blocking operation.
	// 2. By gcAssistAlloc1 to signal back to its caller that the goroutine completed
	//    the GC cycle. It is unsafe to do so in any other way, because the goroutine's
	//    stack may have moved in the meantime.
	// 3. By debugCallWrap to pass parameters to a new goroutine because allocating a
	//    closure in the runtime is forbidden.
	param        unsafe.Pointer
	atomicstatus uint32
	stackLock    uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
	//当前 Goroutine 的唯一标识,对开发者不可见
	goid         int64
	schedlink    guintptr
	//G 阻塞时长
	waitsince    int64      // approx time when the g become blocked
	//阻塞原因
	waitreason   waitReason // if status==Gwaiting
	//抢占标记,其值为true 执行 stackguard0 = stackpreempt
	preempt       bool
	//将抢占标记修改为 _Gpreedmpted,如果修改失败则取消
	preemptStop   bool
	//在同步安全点收缩栈
	preemptShrink bool // shrink stack at synchronous safe point
	//异步安全点;如果 g 在异步安全点停止则设置为true,表示在栈上没有精确的指针信息
	asyncSafePoint bool
	//地址异常引起的panic(代替了崩溃)
	paniconfault bool // panic (instead of crash) on unexpected fault address
	//g 扫描完了栈,受状态 _Gscan 位保护
	gcscandone   bool // g has scanned stack; protected by _Gscan bit in status
	//不允许拆分stack
	throwsplit   bool // must not split stack
	//表示是否有未加锁定的channel指向到了g 栈,
	//如果为true,那么对栈的复制需要channal锁来保护这些区域
	activeStackChans bool
	//表示g 是放在chansend 还是 chanrecv。用于栈的收缩,是一个布尔值,但是原子性更新
	parkingOnChan uint8

	raceignore     int8     // ignore race detection events
	sysblocktraced bool     // StartTrace has emitted EvGoInSyscall about this goroutine
	tracking       bool     // whether we're tracking this G for sched latency statistics
	trackingSeq    uint8    // used to decide whether to track this G
	runnableStamp  int64    // timestamp of when the G last became runnable, only used when tracking
	runnableTime   int64    // the amount of time spent runnable, cleared when running, only used when tracking
	sysexitticks   int64    // cputicks when syscall has returned (for tracing)
	traceseq       uint64   // trace event sequencer
	tracelastp     puintptr // last P emitted an event for this goroutine
	lockedm        muintptr
	sig            uint32
	writebuf       []byte
	sigcode0       uintptr
	sigcode1       uintptr
	sigpc          uintptr
	//创建当前G的pc
	gopc           uintptr         // pc of go statement that created this goroutine
	ancestors      *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)
	//go func 的pc
	startpc        uintptr         // pc of goroutine function
	racectx        uintptr
	waiting        *sudog         // sudog structures this g is waiting on (that have a valid elem ptr); in lock order
	cgoCtxt        []uintptr      // cgo traceback context
	labels         unsafe.Pointer // profiler labels
	//通过time.Sleep 缓存 timer
	timer          *timer         // cached timer for time.Sleep
	selectDone     uint32         // are we participating in a select and did someone win the race?

	// goroutineProfiled indicates the status of this goroutine's stack for the
	// current in-progress goroutine profile
	goroutineProfiled goroutineProfileStateHolder

	// Per-G GC state

	// gcAssistBytes is this G's GC assist credit in terms of
	// bytes allocated. If this is positive, then the G has credit
	// to allocate gcAssistBytes bytes without assisting. If this
	// is negative, then the G must correct this by performing
	// scan work. We track this in bytes to make it fast to update
	// and check for debt in the malloc hot path. The assist ratio
	// determines how this corresponds to scan work debt.
	gcAssistBytes int64
}

Goroutine 的几种状态

  1. 每一个 Goroutine 都有着自己的状态,这些状态由 Goroutine 结构体中的 status 字段来表示:
  1. _Gidle:表示空闲状态,或者刚分配还没有初始化,即没有分配到 P 执行任务,可以被分配执行任务或者被放入全局 runqueue 中等待 P 去执行
  2. _Grunnable: 表示已经被分配到 P,并且可以被调度器进行调度执行,但还没有使用 CPU 执行,因为当前 P 上可能还有其他的 Goroutine 在运行
  3. _Grunning:表示正在被某个 P 执行,已经占用了 CPU ,处于执行状态。
  4. _Gsyscall:表示正在通过系统调用等方式阻塞等待某个事件发生,例如等待 I/O 完成、等待信号量等。此时,该 Goroutine 处于阻塞状态,不会占用 P 的资源。
  5. _Gwaiting:表示正在等待某个锁或条件变量等同步工具解除阻塞,此时,该 Goroutine 会被放入等待队列当中。
  6. _Gmoribund_gc:表示正在被 GC 找出需要清理的 Goroutine 的过程中,此时该 Goroutine 已经不再活跃
  7. _Gdead:表示已经死亡,可以被 GC 回收。但它还没有被从所有数据结构中删除,因为可能在某些队列中等待被移除。
  8. _Gcopystack:表示正在进行栈拷贝,此时该 Goroutine 的栈已经满了,需要扩容。
  9. _Gpreempted:表示被抢占,因为存在更高优先级的任务需要执行。
  10. _Gscan: 新版本中已经被移除,旧版本中垃圾收集时,用来表示正在被扫描,防止 GC 重复扫描同一个 Goroutine
  1. 需要注意的是对于 _Gmoribund_unused与_Genqueue_unused 状态并未使用
  2. 还有GC时出现的几种特殊的状态,表示 GC 正在扫描栈。Goroutine 不会执行用户代码,且栈由设置了 _Gscan 位的 Goroutine 所有
  1. _Gscanrunnable:表示该 Goroutine 正在被 GC 扫描,但已经就绪可以被调度器调度执行。
  2. _Gscanrunning:表示该 Goroutine 正在被 GC 扫描,同时也正在运行中。
  3. _Gscansyscall:表示该 Goroutine 正在进行系统调用等操作而被 GC 暂停扫描的状态。
  4. _Gscanwaiting:表示该 Goroutine 正在等待某个锁或条件变量等同步工具解除阻塞,在此期间被 GC 暂停扫描的状态。
  5. _Gscanpreempted:表示该 Goroutine 正在被 GC 扫描,同时它被系统抢占了,也就是存在更高优先级的任务需要执行

gbuf 结构体

  1. 主要存储一些寄存器信息,如sp、pc 和 g 的偏移量
  2. 调度器在将 G 由一种状态变更为另一种状态时,需要将上下文信息保存到这个gobuf结构体,当再次运行 G 的时候,再从这个结构体中读取出来,主要用来暂时上下文信息。其中的栈指针和程序计数器会用来存储或者恢复寄存器中的值,改变程序即将执行的代码
type gobuf struct {
	//栈指针位置
	sp   uintptr
	//程序计数器,运行到的程序位置
	pc   uintptr
	g    guintptr
	//可能是一个分配在heap的函数变量,因此GC 需要追踪它,
	//不过它有可能需要设置并进行清除,在有写屏障 的时候有些困难
	ctxt unsafe.Pointer
	//系统调用的结果
	ret  uintptr
	lr   uintptr
	//未知
	bp   uintptr // for framepointer-enabled architectures
}

总结

  1. 每个G 都有自己的状态,保存在 atomic status 字段,共有十几种状态值,状态发生变化时status 字段值被改变
  2. 每个 G 都需要保存当前G的上下文的信息,这个信息存储在 sched 字段,其数据类型为gobuf,想理解存储的信息可以看一下这个结构体的各个字段
  3. 每个G 都有三个与抢占有关的字段,分别为 preempt、preemptStop 和 premptShrink
  4. 每个 G 都有自己的唯一id, 字段为goid,但此字段官方不推荐开发使用
  5. 每个 G 都可以最多绑定一个m,如果可能未绑定,则值为 nil
  6. 每个 G 都有自己内部的 defer 和 panic。
  7. G 可以被阻塞,并存储有阻塞原因,字段 waitsince 和 waitreason
  8. G 可以被进行 GC 扫描,相关字段为 gcscandone,atomicstatus

p 结构体

  1. P表示逻辑处理器,对 G 来说,P 相当于 CPU,G 只有绑定到 P 才能被调度。对 M 来说,P 提供了相关的执行环境Context,如内存分配状态(mcache),任务队列(G)等。
  2. P 的数量决定了系统内最大可并行的 G 的数量(前提:物理 CPU 核数 >= P 的数量)默认是cpu核数,可以 GoMAXPROCS 设置,但是不论 GoMAXPROCS 设置为多大,P 的数量最大为 256
  3. p中字段重点关注部分
  1. runq: 用来保存等待运行的 G 列表,最大256实际再加上runnext 一个P最大的情况下可以有257个goroutine
  2. runnext : 这个字段是用来实现调度器亲和性的,我们知道原来一个G阻塞时,这时P会再获取一个G进行绑定执行,如果这时原来的G执行阻塞结束时,如果想再次执行,就需要重新在P的runq进行排队,如果当前runq里有太多的工作会造成占用线程的话会导致这个解除阻塞的G迟迟无法执行,同时还有可能被其他处理器所窃取。从 Go 1.5 开始得益于 P 的特殊属性,从阻塞 channel 返回的 Goroutine 会优先运行,这里只需要将这个G放在 runnext 这个字段即可
  3. 每个P都有一个自己的runq,除了自身有的runq 还有一个全局的runq, 对于每个了解过GPM的gohper应该都知道这一点
  4. 每个P下面runq的允许的最大goroutine 数量为256,再加上runnext 一个P最大的情况下可以有257个goroutine
  5. 每个P中还存在一个gFree 内部保存了空闲的 G 列表,是个结构体,其中n表示空闲g的个数
type p struct {
	//P的唯一标识
	id          int32
	//P当前状态,状态值有_Pidle、_Prunning、_Psyscall、_Pgcstop 和 _Pdead
	status      uint32 // one of pidle/prunning/...
	link        puintptr
	//每次程序被调用时递增
	schedtick   uint32     // incremented on every scheduler call
	//每次系统调用时时递增
	syscalltick uint32     // incremented on every system call
	//最后tick的时间
	sysmontick  sysmontick // last tick observed by sysmon
	//当前正在绑定的m, 有可能为空,如_Pidle状态时
	m           muintptr   // back-link to associated m (nil if idle)
	//每个p的小对象缓存,无锁
	mcache      *mcache
	//页面缓存,无锁
	pcache      pageCache
	//race相关
	raceprocctx uintptr

	deferpool    []*_defer // pool of available defer structs (see panic.go)
	deferpoolbuf [32]*_defer

	// Cache of goroutine ids, amortizes accesses to runtime·sched.goidgen.
	//下面两个属性都是 goroutine ids 的缓存
	goidcache    uint64
	goidcacheend uint64

	//运行队列头
	runqhead uint32
	// 运行队列尾
	runqtail uint32
	//运行队列, 数组类型,最大值为256
	runq     [256]guintptr
	//当前P(进入运行状态时)立即要运行的goroutine,可能为nil
	//如果此字段不为nil 的话,则表示下次待运行的状态为 runnable 的 goroutine,
	//此时则不从 runq 中获取G,优先级高于runq。如果当前G 还有剩余的可用时间,
	//那么就运行这个 runnext 的G 继承剩下的时间。此字段的调用请参考函数 runtime.runqget()
	runnext guintptr

	//空闲的g, gFree.n 表示空闲G的个数,
	//字段主要存储空闲G,方便实现对G的复用(只有当状态为 Gdead 时才有效
	gFree struct {
		gList
		n int32
	}
	//缓存相关
	sudogcache []*sudog
	//sudog 缓冲区
	sudogbuf   [128]*sudog

	// Cache of mspan objects from the heap.
	mspancache struct {
		// We need an explicit length here because this field is used
		// in allocation codepaths where write barriers are not allowed,
		// and eliminating the write barrier/keeping it eliminated from
		// slice updates is tricky, moreso than just managing the length
		// ourselves.
		len int
		buf [128]*mspan
	}

	tracebuf traceBufPtr

	// traceSweep indicates the sweep events should be traced.
	// This is used to defer the sweep start event until a span
	// has actually been swept.
	traceSweep bool
	// traceSwept and traceReclaimed track the number of bytes
	// swept and reclaimed by sweeping in the current sweep loop.
	traceSwept, traceReclaimed uintptr

	palloc persistentAlloc // per-P to avoid mutex

	_ uint32 // Alignment for atomic fields below

	// The when field of the first entry on the timer heap.
	// This is updated using atomic functions.
	// This is 0 if the timer heap is empty.
	//原子函数操作,如果 timer heap 为空,则值为 0
	timer0When uint64

	// The earliest known nextwhen field of a timer with
	// timerModifiedEarlier status. Because the timer may have been
	// modified again, there need not be any timer with this value.
	// This is updated using atomic functions.
	// This is 0 if there are no timerModifiedEarlier timers.
	timerModifiedEarliest uint64

	// Per-P GC state
	gcAssistTime         int64 // Nanoseconds in assistAlloc
	gcFractionalMarkTime int64 // Nanoseconds in fractional mark worker (atomic)

	// limiterEvent tracks events for the GC CPU limiter.
	limiterEvent limiterEvent

	// gcMarkWorkerMode is the mode for the next mark worker to run in.
	// That is, this is used to communicate with the worker goroutine
	// selected for immediate execution by
	// gcController.findRunnableGCWorker. When scheduling other goroutines,
	// this field must be set to gcMarkWorkerNotWorker.
	gcMarkWorkerMode gcMarkWorkerMode
	// gcMarkWorkerStartTime is the nanotime() at which the most recent
	// mark worker started.
	//gc的开始时间
	gcMarkWorkerStartTime int64

	// gcw is this P's GC work buffer cache. The work buffer is
	// filled by write barriers, drained by mutator assists, and
	// disposed on certain GC state transitions.
	//GC work的 buffer, 写屏障填充,结构体 gcWork
	gcw gcWork

	// wbBuf is this P's GC write barrier buffer.
	//
	// TODO: Consider caching this in the running G.
	// P的GC写屏障buffer
	wbBuf wbBuf

	//如果为1,则在下一个安全点运行 sched.safePointFn
	runSafePointFn uint32 // if 1, run sched.safePointFn at next safe point

	// statsSeq is a counter indicating whether this P is currently
	// writing any stats. Its value is even when not, odd when it is.
	statsSeq uint32

	// Lock for timers. We normally access the timers while running
	// on this P, but the scheduler can also do it from a different P.
	//timer锁
	timersLock mutex

	// Actions to take at some time. This is used to implement the
	// standard library's time package.
	// Must hold timersLock to access.
	timers []*timer

	// Number of timers in P's heap.
	// Modified using atomic instructions.
	//P堆中的timer数
	numTimers uint32

	// Number of timerDeleted timers in P's heap.
	// Modified using atomic instructions.
	//p堆中 deletedTimers timer 的数量
	deletedTimers uint32

	// Race context used while executing timer functions.
	//执行计时器函数时使用的竞争上下文
	timerRaceCtx uintptr

	// maxStackScanDelta accumulates the amount of stack space held by
	// live goroutines (i.e. those eligible for stack scanning).
	// Flushed to gcController.maxStackScan once maxStackScanSlack
	// or -maxStackScanSlack is reached.
	maxStackScanDelta int64

	// gc-time statistics about current goroutines
	// Note that this differs from maxStackScan in that this
	// accumulates the actual stack observed to be used at GC time (hi - sp),
	// not an instantaneous measure of the total stack size that might need
	// to be scanned (hi - lo).
	scannedStackSize uint64 // stack size of goroutines scanned by this P
	scannedStacks    uint64 // number of goroutines scanned by this P

	// preempt is set to indicate that this P should be enter the
	// scheduler ASAP (regardless of what G is running on it).
	//抢占标记,如果值为true,表示 p 进入调度(不管G在运行什么)
	preempt bool

	// Padding is no longer needed. False sharing is now not a worry because p is large enough
	// that its size class is an integer multiple of the cache line size (for any of our architectures).
}

p的五种状态

  1. _Pidle:表示该 P 处于空闲状态,没有绑定任何的 Goroutine。
  2. _Prunning:表示该 P 正在运行某个 Goroutine。
  3. _Psyscall:表示该 P 正在执行系统调用。
  4. _Pgcstop:表示该 P 被停止了,进行垃圾回收操作。
  5. _Pdead:表示该 P 已经死亡,无法再执行任务

总结:

  1. 每个P都有自己的状态,分别为 _Pidle、 _Prunning 、_Psyscall 、_Pgcstop 、_Pdead
  2. 每个P都存储有自己被调度次数和系统调用的次数,字段 schedtick 和 syscalltick
  3. P 可以指定一个M, 但也有不指定,这时m值为nil,字段m
  4. 每个P 都有一个自己的runq,用来存放可以 runnable 状态的 goroutines, 最多可以存放256个 goroutine。一般在介绍GMP关系时,我们称之为 local queue,当然还有一个global queue
  5. 每个P都可能有一个 runnext 的 goroutine。如果此字段不为nil,则P下次执行G的时候,优先执行此字段的goroutine(如果P的状态从_Pidle变为_Prunning时此字段的值是变化吗?)
  6. P可以缓存goroutine,字段 goidcache
  7. P的GC状态
  8. P可以有多个timer, 以slice 形式存储, 字段 timers
  9. P可以缓存堆上面的mspan对象,mspan对象是什么?
  10. P 有一个抢占标记,字段为 preempt。如果为ture ,则表示P立即进入调度
  11. P结构体使用了pad, 以优化cpu, 解决cpu伪共享的问题

m 结构体

  1. M 是指OS 内核线程,代表着真正执行计算的资源,在绑定有效的 P 后,进入 schedule 循环;而 schedule 循环的机制大致是从 Global 队列、P 的 Local 队列以及 wait 队列中获取
  2. 默认最大限制为 10000 个。如果一个M工作完成后,找不到可用的P,则需要将自己休眠,并放在空闲线程中,等待下次使用。
  3. M 并不保留 G 状态,这是 G 可以跨 M 调度的基础
  4. 其中几个需要注意的字段
  1. 与goroutine有关的字段有caughtsig 和 curg,其中 curg 这个就是当前m绑定的goroutine
  2. 与p相关的字段的p、nextp、oldp,分别表示当前绑定的P、下次绑定的P和上次绑定的P,nextp 和oldp
  3. 与cgo相关: ncgocall, ncgo, cgoCallersUse, cgoCaller
  4. 与调度相关有关的字段(见gppark()函数): waitlockf、 waitlock 、waittraceev、waittraceskip
type m struct {
	//1.g0:所有调用栈的Goroutine,这是一个比较特殊的Goroutine
    //普通的Goroutine栈是在Heap分配的可增长的stack,而g0的stack是M对应的线程栈
    //所有调度相关代码,会先切换到该Goroutine的栈再执行。
	g0      *g     // goroutine with scheduling stack
	//2.morebuf:堆栈扩容使用(见这里),gobuf 数据类型,
	//gobuf这个数据结构在g结构体中已出现过,
	//它的作用就是保存一个g 的上下文数据。这里作为传递给 morestack 的参数
	morebuf gobuf  // gobuf arg to morestack
	divmod  uint32 // div/mod denominator for arm - known to liblink
	_       uint32 // align next field to 8 bytes

	// Fields not known to debuggers.
	procid        uint64            // for debuggers, but offset not hard-coded
	gsignal       *g                // signal-handling g
	goSigStack    gsignalStack      // Go-allocated signal handling stack
	sigmask       sigset            // storage for saved signal mask
	tls           [tlsSlots]uintptr // thread-local storage (for x86 extern register)
	mstartfn      func()
	//当前正在运行的 goroutine
	curg          *g       // current running goroutine
	//在致命信号期间运行的goroutine
	caughtsig     guintptr // goroutine running during fatal signal
	//用于执行go code 的 p,就是当前正在m绑定的P,如果没有运行code 的话,值为nil
	p             puintptr // attached p for executing go code (nil if not executing go code)
	//下次运行时的P
	nextp         puintptr
	//在执行系统调用之前绑定的P
	oldp          puintptr // the p that was attached before executing a syscall
	//m的唯一id
	id            int64
	mallocing     int32
	throwing      throwType
	//如果非空,则保持curg 运行在当前m
	preemptoff    string // if != "", keep curg running on this m
	locks         int32
	dying         int32
	profilehz     int32
	//表示当前m空闲,需要找一个新的工作来执行
	//对于 spinning 这个情况经常见,当前m没有活干了,需要努力找一个新活干,属于GMP调度中的一个关系点
	spinning      bool // m is out of work and is actively looking for work
	//在 note 阻塞
	blocked       bool // m is blocked on a note
	//在一个C 线程被调用 sigaltstack
	newSigstack   bool // minit on C thread called sigaltstack
	printlock     int8
	//当前m正在执行一个cgo调用
	incgo         bool   // m is executing a cgo call
	//如果值为0,则需要安全的释放go并删除m(原子操作)
	freeWait      atomic.Uint32 
	fastrand      uint64
	needextram    bool
	traceback     uint8
	//cgo的总调用次数
	ncgocall      uint64 
	//目前正在执行的cgo调用数
	ncgo          int32
	//如果 > 0,临时使用cgoCallers
	cgoCallersUse uint32      // if non-zero, cgoCallers in use temporarily
	cgoCallers    *cgoCallers // cgo traceback if crashing in cgo call
	//数据类型为 note , M休眠时使用的信号量, 唤醒M时会通过它唤醒
	park          note
	alllink       *m // on allm
	//调度相关,下一个m, 当m在链表结构中会使用
	schedlink     muintptr
	//锁定的goroutine, g.lockedm的对应值
	lockedg       guintptr
	//创建当前线程的栈
	createstack   [32]uintptr // stack that created this thread.
	//跟踪外部锁线程
	lockedExt     uint32      // tracking for external LockOSThread
	//跟踪内部锁线程
	lockedInt     uint32      // tracking for internal lockOSThread
	//下一个等待此锁的m
	nextwaitm     muintptr    // next m waiting for lock
	//等待解锁函数
	waitunlockf   func(*g, unsafe.Pointer) bool
	//等待锁
	waitlock      unsafe.Pointer
	waittraceev   byte
	waittraceskip int
	startingtrace bool
	syscalltick   uint32
	//m释放列表,链接到 sched.freem 字段
	freelink      *m // on sched.freem

	// these are here because they are too large to be on the stack
	// of low-level NOSPLIT functions.
	libcall   libcall
	libcallpc uintptr // for cpu profiler
	libcallsp uintptr
	libcallg  guintptr
	syscall   libcall // stores syscall parameters on windows
	//属于VDSO调用,vdsoSP 如果值为0,则表示没有调用
	vdsoSP uintptr // SP for traceback while in VDSO call (0 if not in call)
	vdsoPC uintptr // PC for traceback while in VDSO call

	// preemptGen counts the number of completed preemption
	// signals. This is used to detect when a preemption is
	// requested, but fails. Accessed atomically.
	//完成的抢占信号数量
	preemptGen uint32
	//是不是当前M上挂起的抢占信号
	signalPending uint32

	dlogPerM

	mOS

	//当前持有锁的数量
	locksHeldLen int
	//最多可以持有1个锁,数据类型为 heldLockInfo
	locksHeld    [10]heldLockInfo
}

总结

  1. 每个 m 都持有一个特殊的goroutine,我们称之为g0,它具有调度栈的能力。调度和执行系统调用时会先切换到这个g,多个m中的g0指向的是同一个
  2. m 可以与一个G相绑定,字段 curg
  3. m 可以与P绑定,也可以不绑定,如果已绑定则字段p不非nil
  4. m 可以记录下次和上次绑定的p,字段 nextp 和 oldp
  5. 如果m 没有工作可做的话,它会通过自旋积极再找一个活干
  6. m会不会在 note 阻塞
  7. 可以记录对m完成抢占信息的次数
  8. 一个m最多可以持有10个锁

schedt 调度器结构体

  1. Go 调度器,它维护有存储 M 和 G 的队列以及调度器的一些状态信息等,全局调度时使用,调度器循环的机制大致是从各种队列、P 的本地队列中获取 G,然后切换到 G 的执行栈上并执行 G 的函数,调用 Goexit 做清理工作并回到 M,如此反复
  2. 重点字段
  1. 调度器会记录当前哪个m在等待工作(midle),一共有多少个空闲m(nmidle),同时还记录等待工作的锁定m的数量(nmidlelocked), 已创建m的数量和允许的最大空间m数量(maxmcount)等信息
  2. 记录了当前空闲的P是哪一个(pidle),共有几少个空闲的P(npidle),还有多少个正在spinning 的M(nmspinning)。另外还有一个全局G的列队(runq)。是不是越来越有点意思了,有了空闲的M(midle)及其数量(nmidle),也有了空闲的P(pidle)及其数量(npidle),还知道有多少个M正在在spinning(nmspinning)拼命在找活干,除了每个P下面自己的runq队列,调度器自身也有存放G的队列runq,所需要的资源GPM都够了,剩下的就是看如何给他们近排活干了
  3. gFree 全局缓存G, 匿名结构体。如果一个G完毕后,并不立即释放它而是先放在一个列表里,以备后续复用,这样可以优先减少创建资源的开销。放入g后再检查一下当前 p 的 p.gFree 长度是否>=64(p.gFree.n >= 64) , 如果大于则会将 p.gFree 一半的 g 迁移到 sched.gFree,源码见 gfput()`
type schedt struct {
	//原子访问, 最顶部,保证32位系统下的对齐
	goidgen   uint64
	//上次网络轮询的时间,如果当前正在轮询,则为0
	lastpoll  uint64 // time of last network poll, 0 if currently polling
	//当前轮询休眠的时间
	pollUntil uint64 // time to which current poll is sleeping

	lock mutex

	// When increasing nmidle, nmidlelocked, nmsys, or nmfreed, be
	// sure to call checkdead().

	//空闲m等待队列
	midle        muintptr // idle m's waiting for work
	//空闲m的数量
	nmidle       int32    // number of idle m's waiting for work
	//等待工作的锁定m的数量
	nmidlelocked int32    // number of locked m's waiting for work
	//已创建的m数和下一个m ID, 一个字段代表两个意义
	mnext        int64    // number of m's that have been created and next M ID
	//允许的最大m数
	maxmcount    int32    // maximum number of m's allowed (or die)
	//死锁不计算系统m的数量
	nmsys        int32    // number of system m's not counted for deadlock
	//累计已释放m的数量
	nmfreed      int64    // cumulative number of freed m's
	//系统goroutins的数量,原子更新
	ngsys uint32 // number of system goroutines; updated atomically
	//空闲p
	pidle      puintptr // idle p's
	npidle     uint32
	//自旋, 查看proc.go 文件的 "Worker thread parking/unparking" 注释
	nmspinning uint32 // See "Worker thread parking/unparking" comment in proc.go.

	// Global runnable queue.
	//全局运行队列信息, gQueue 是一个通过 g.schedlink 链接的双向队列
	runq     gQueue
	//队列大小
	runqsize int32

	// disable controls selective disabling of the scheduler.
	//
	// Use schedEnableUser to control this.
	//
	// disable is protected by sched.lock.
	disable struct {
		// user disables scheduling of user goroutines.
		user     bool
		runnable gQueue // pending runnable Gs
		n        int32  // length of runnable
	}

	// Global cache of dead G's.
	//全局缓存G
	gFree struct {
		lock    mutex
		stack   gList // Gs with stacks
		noStack gList // Gs without stacks
		n       int32
	}

	// Central cache of sudog structs.
	sudoglock  mutex
	sudogcache *sudog

	// Central pool of available defer structs.
	deferlock mutex
	deferpool *_defer

	// freem is the list of m's waiting to be freed when their
	// m.exited is set. Linked through m.freelink.
	//freem 是当 他们的 m.exited 被设置时的等待被释放m列表
	//通过 m.freelink 链接
	freem *m
	//gc正在等待运行
	gcwaiting  uint32 // gc is waiting to run
	stopwait   int32
	stopnote   note
	sysmonwait uint32
	sysmonnote note

	// safepointFn should be called on each P at the next GC
	// safepoint if p.runSafePointFn is set.
	//如果 p.runSafePointFn 设置的话, safeopintFn 将在每个p下次 GC safepoint 时被调用
	safePointFn   func(*p)
	safePointWait int32
	safePointNote note

	profilehz int32 // cpu profiling rate
	//对gomaxprocs的最后更改时间
	procresizetime int64 // nanotime() of last change to gomaxprocs
	totaltime      int64 // ∫gomaxprocs dt up to procresizetime

	// sysmonlock protects sysmon's actions on the runtime.
	//
	// Acquire and hold this mutex to block sysmon from interacting
	// with the rest of the runtime.
	//在运行时,sysmonlock保护 sysmon 的运行
	sysmonlock mutex

	// timeToRun is a distribution of scheduling latencies, defined
	// as the sum of time a G spends in the _Grunnable state before
	// it transitions to _Grunning.
	//
	// timeToRun is protected by sched.lock.
	timeToRun timeHistogram
}

总结

  1. 调度器会记录当前是否处于轮训状态以及轮训的时间
  2. 记录有M相关的信息,如当前空闲的M,如果有多个的话,也会记录个数,记录的还有已用过数量,当前有多少个正在spinning,最大允许的M数量。同时也会记录其中持有锁的数量
  3. 记录有P相关的信息,如当前空闲P,空闲的数量。
  4. 持有当前系统 groutine 数量
  5. 有一个G的全局运行队列及其队列大小
  6. 通过gFree 记录有多少个空闲的G,可以被重复利用
  7. sudog缓存 和 deferpool
  8. 都有一个全局锁(lock)和 sysmon (sysmonlock)锁及其它锁(sugoglock)
    10 可以控制是否禁用用户gorutine的调度行为,字段 disable(调用 schedEnableUser)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值