0%

【golang源码分析】之GPM概述

  我们知道 golang 原生支持并发,goroutine 就是其设计的核心。 goroutine 是用户态线程,也就是协程。这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的,当然这不需要我们做什么工作,golang 已经帮我们处理了。在如今处处高并发的时代,很多库包括语言都有协程的影子。例如,腾讯的 libco 是微信后台大规模使用的c/c++协程库;lua 也是有协程的。

进程线程协程

  为什么要多此一举来设计协程呢?还要自己来负责调度。

  • 进程:分配完整独立的地址空间,拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程的切换只发生在内核态,由操作系统调度。
  • 线程:和其它本进程的线程共享地址空间,拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度(标准线程是的)。
  • 协程:和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。

  进程切换分两步:

  1. 切换页目录以使用新的地址空间
  2. 切换内核栈和硬件上下文

  对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。

  切换的性能消耗:

  1. 线程上下文切换和进程上下文切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。

  2. 另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor’s Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。

  进程和线程的切换主要依赖于时间片的控制,而协程的切换则主要依赖于自身,这样的好处是避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任。另外,协程一般拥有更小的创建,销毁和切换代价,以及更合理的内存分配,这也进一步提高性能。

Goroutine 与线程

  我们这里从内存占用创建销毁切换代价来对比下 Goroutine 与线程,来了解轻量的 Goroutine 。

内存占用

  创建一个 goroutine 的栈内存消耗为 2KB(_StackMin = 2048),实际运行过程中,如果栈空间不够用,会自动进行扩容,最大 1GB(maxstacksize = 1 << 20) 。创建一个 thread 则默认需要消耗 8 MB (64bit系统) 栈内存,而且还需要一个被称为 “a guard page” 的区域用于和其他 thread 的栈空间进行隔离。线程的栈大小在可以 ulimit -s 查看或设置,也可以在 /etc/security/limits.conf 中配置,并且是固定的。而 goroutine 是动态栈,会根据需要动态地伸缩,小到 2KB 大到 1GB ,所以可以创建大量的 goroutine 。

创建销毁

  Thread 创建和销毀都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池。而 goroutine 因为是由 Go runtime 负责管理的,创建和销毁的消耗非常小,是用户级。

切换代价

线程切换大概在几微秒级别,协程切换大概在百 ns 级别。

Thread

  线程切换过程:

  1. 进入系统调用
  2. 调度器本身代码执行
  3. 线程上下文切换: PC, SP 等寄存器,栈,线程相关的一些局部变量,还涉及一些 cache miss 的情况;
  4. 退出系统调用

  当 threads 切换时需要保存的寄存器:

  16 general purpose registers, PC (Program Counter), SP (Stack Pointer), segment registers, 16 XMM registers, FP coprocessor state, 16 AVX registers, all MSRs etc.

Goroutines

  goroutines 切换只需保存三个寄存器:

  Program Counter, Stack Pointer and BP。以及一些其它的信息,在 gobuf 结构体中

早期的 GM 模型

  在 go 1.0 版本, 只有 GM ,而没有 P , 只有一个全局可运行队列。

GM 模型

  存在一些缺陷:

  • 调度锁的粒度,一个全局的调度锁。创建、销毁、调度 G 都需要每个M获取锁,这就形成了激烈的锁竞争。
  • 就绪的 G 被 M 取出执行的时候,并不一定是之前运行或创建 G 的那个 M ,这样线程局部性遭到破坏。
  • 系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销。
  • Go 内存分配类似 TCMalloc ,每个 M 都有 Thread Local Storage,每个 M 都有一些内存 cache ,只有运行的 G 的时候才需要这些 cache ,这样阻塞的 M 其实占用很大的内存。

  后来 Go 1.1 大神 Dmitry Vyokov 增加了 P 的概念,表示执行 G 所需的资源,当 M 和 P 绑定后才能执行 G ,比如 mcache ,本地队列等。

GPM 模型

  主要的概念是:

  • G: Goroutine,即我们在 Go 程序中使用 go 关键字创建的执行体。
  • M: Machine,或 worker thread,即传统意义上进程的线程;
  • P: processor,一种执行 Go 代码被要求资源。M 必须关联一个 P 才能执行 Go 代码,但它可以被阻塞或在一个系统调用中没有关联的 P。默认为机器核数,可通过环境变量 GOMAXPROCS 设置。

GPM 模型

  主要优化点:

  • mcache 从 M 移动到 P 中。
  • 每个 P 拥有本地运行队列,新建的 G 放到本地队列,只有当本地满了再批量放入到全局队列。获取的时候优先从本地队列取。
  • 当本地队列没有 G 的时候,会从其它的地方窃取 (findrunnable() 函数),比如:本地队列,全局队列, netpoll , p.runnext 等。

主要结构体

  这里涉及下主要的几个结构体。

G

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// src/runtime/runtime2.go
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).
// Stack 参数
// stack 描述了实际的栈内存:[stack.lo, stack.hi)
// stackguard0 是对比 Go 栈增长的 prologue 的栈指针,如果 sp 寄存器比 stackguard0 小(由于栈往低地址方向增长),会触发栈拷贝和调度
// 通常情况下:stackguard0 = stack.lo + StackGuard,但被抢占时会变为 StackPreempt。
// stackguard1 是对比 C 栈增长的 prologue 的栈指针,当位于 g0 和 gsignal 栈上时,值为 stack.lo + StackGuard
// 在其他栈上值为 ~0 用于触发 morestackc (并 crash) 调用
//
// prologue (序言) 函数是函数开头的几行代码,它们准备了堆栈和寄存器以供在函数内使用。 epilogue (尾声) 函数出现在函数的末尾,并将堆栈和寄存器恢复到调用函数之前的状态。
// prologue/epilogue 参见:https://en.wikipedia.org/wiki/Function_prologue
//
// 编译器会在有栈溢出风险的函数开头加如 一些代码(也就是prologue),会比较 SP (栈寄存器,指向栈顶) 和 stackguard0,如果 SP 的值更小,说明当前 g 的栈要用完了,
// 有溢出风险,需要调用 morestack_noctxt 函数来扩栈,morestack_noctxt()->morestack()->newstack() ,newstack中会处理抢占(preempt)。参见 asm_amd64.s,stack.go
stack stack // offset known to runtime/cgo // 当前g使用的栈空间, 有lo和hi两个成员
stackguard0 uintptr // offset known to liblink // stackguard0 = stack.lo + StackGuard ,检查栈空间是否足够的值, 低于这个值会扩张栈, 用于 GO 的 stack overlow的检测
stackguard1 uintptr // offset known to liblink // stackguard1 = stack.lo + StackGuard ,检查栈空间是否足够的值, 低于这个值会扩张栈, 用于 C 的 stack overlow的检测

_panic *_panic // innermost panic - offset known to liblink // 内部 panic ,偏移量用于 liblink
_defer *_defer // innermost defer // 内部 defer
m *m // current m; offset known to arm liblink // 当前 g 对应的 m ; 偏移量对 arm liblink 透明
sched gobuf // goroutine 的现场,g 的调度数据, 当 g 中断时会保存当前的 pc 和 sp 等值到这里, 恢复运行时会使用这里的值
syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc // 如果 status==Gsyscall, 则 syscallsp = sched.sp 并在 GC 期间使用
syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc // 如果 status==Gsyscall, 则 syscallpc = sched.pc 并在 GC 期间使用
stktopsp uintptr // expected sp at top of stack, to check in traceback // 期望 sp 位于栈顶,用于回溯检查
param unsafe.Pointer // passed parameter on wakeup // wakeup 唤醒时候传递的参数
atomicstatus uint32 // g 的当前状态,原子性
stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus // sigprof/scang锁,将会归入到atomicstatus
goid int64 // goroutine ID
schedlink guintptr // 下一个 g , 当 g 在链表结构中会使用
waitsince int64 // approx time when the g become blocked // g 阻塞的时间
waitreason waitReason // if status==Gwaiting // 如果 status==Gwaiting,则记录等待的原因
preempt bool // preemption signal, duplicates stackguard0 = stackpreempt // 抢占信号, g 是否被抢占中, stackguard0 = stackPreempt 的副本
paniconfault bool // panic (instead of crash) on unexpected fault address // 发生 fault panic (不崩溃)的地址
preemptscan bool // preempted g does scan for gc // 抢占式 g 会执行 GC scan
gcscandone bool // g has scanned stack; protected by _Gscan bit in status // g 执行栈已经 scan 了;此此段受 _Gscan 位保护
gcscanvalid bool // false at start of gc cycle, true if G has not run since last scan; TODO: remove? // 在 gc 周期开始时为 false,如果自上次 scan 以来G没有运行,则为 true
throwsplit bool // must not split stack // 必须不能进行栈分段
raceignore int8 // ignore race detection events // 忽略 race 检查事件
sysblocktraced bool // StartTrace has emitted EvGoInSyscall about this goroutine // StartTrace 已经出发了此 goroutine 的 EvGoInSyscall
sysexitticks int64 // cputicks when syscall has returned (for tracing) // 当 syscall 返回时的 cputicks(用于跟踪)
traceseq uint64 // trace event sequencer // 跟踪事件排序器
tracelastp puintptr // last P emitted an event for this goroutine // 最后一个为此 goroutine 触发事件的 P
lockedm muintptr // g 是否要求要回到这个 M 执行, 有的时候 g 中断了恢复会要求使用原来的 M 执行
sig uint32 // 信号,参见 defs_linux_arm64.go : siginfo
writebuf []byte // 写缓存
sigcode0 uintptr // 参见 siginfo
sigcode1 uintptr // 参见 siginfo
sigpc uintptr // 产生信号时的PC
gopc uintptr // pc of go statement that created this goroutine // 当前创建 goroutine go 语句的 pc 寄存器
ancestors *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors) // 创建此 goroutine 的 ancestor (祖先) goroutine 的信息(debug.tracebackancestors 调试用)
startpc uintptr // pc of goroutine function // goroutine 函数的 pc 寄存器
racectx uintptr // 竟态上下文
waiting *sudog // sudog structures this g is waiting on (that have a valid elem ptr); in lock order // 如果 g 发生阻塞(且有有效的元素指针)sudog 会将当前 g 按锁住的顺序组织起来
cgoCtxt []uintptr // cgo traceback context // cgo 回溯上下文
labels unsafe.Pointer // profiler labels // 分析器标签
timer *timer // cached timer for time.Sleep // 为 time.Sleep 缓存的计时器
selectDone uint32 // are we participating in a select and did someone win the race? // 我们是否正在参与 select 且某个 goroutine 胜出

// 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 是该 G 在分配的字节数这一方面的的 GC 辅助 credit (信誉)
// 如果该值为正,则 G 已经存入了在没有 assisting 的情况下分配了 gcAssistBytes 字节,如果该值为负,则 G 必须在 scan work 中修正这个值
// 我们以字节为单位进行追踪,一遍快速更新并检查 malloc 热路径中分配的债务(分配的字节)。assist ratio 决定了它与 scan work 债务的对应关系
gcAssistBytes int64
}

P

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// src/runtime/runtime2.go
type p struct {
lock mutex // 锁

id int32 // ID
status uint32 // one of pidle/prunning/... // 状态
link puintptr // p 的链表
schedtick uint32 // incremented on every scheduler call // 每次调度程序调用时增加
syscalltick uint32 // incremented on every system call // 每次系统调用时增加
sysmontick sysmontick // last tick observed by sysmon // sysmon观察到的最后一个tick,会记录
m muintptr // back-link to associated m (nil if idle) // 反向链接到相关的M(如果空闲则为nil)
mcache *mcache // mcache: 每个P的内存缓存,不需要加锁
racectx uintptr // 竟态上下文

deferpool [5][]*_defer // pool of available defer structs of different sizes (see panic.go) // defer池,拥有不同的尺寸
deferpoolbuf [5][32]*_defer

// Cache of goroutine ids, amortizes accesses to runtime·sched.goidgen.
// goroutine ID的缓存将分摊对runtime.sched.goidgen的访问。
goidcache uint64
goidcacheend uint64

// Queue of runnable goroutines. Accessed without lock.
// 可运行goroutine队列。 无锁访问。
// runqhead 和 runqtail 都是 uint32 ,不用担心越界问题,超过最大值之后,会变为 0 ,并且 0 - ^uint32(0) = 1 ,也就是说取长度也不会有问题。
runqhead uint32 // 队头,runqhead 在后头跟,指向第一个可以使用,应该从这里取出
runqtail uint32 // 队尾,runqtail 在队列前面走,指向第一个空槽,应该插入到这里
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(如果不是nil)是当前G准备好的可运行G,如果正在运行的G的时间片中还有剩余时间,则应在下一个运行,而不是在runq中的G来运行。
// 它将继承当前时间片中剩余的时间。如果将一组goroutine锁定为“通信等待”模式,则将其设置为一个单元进行调度,并消除了(可能较大的)调度
// 延迟,而这种延迟可能是由于将就绪的goroutine添加到运行队列的末尾而引起的。
runnext guintptr

// Available G's (status == Gdead)
// 可用的G,状态为Gdead
gFree struct {
gList
n int32
}

sudogcache []*sudog // sudog缓存,初始化为: sudogbuf[:0]
sudogbuf [128]*sudog

tracebuf traceBufPtr // trace缓存,64Kb

// 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指示清扫事件是否被trace。这用于推迟清扫开始事件,直到实际扫过一个span为止。
traceSweep bool
// traceSwept and traceReclaimed track the number of bytes
// swept and reclaimed by sweeping in the current sweep loop.
// traceSwept和traceReclaimed跟踪通过在当前清扫循环中进行清扫来清除和回收的字节数。
traceSwept, traceReclaimed uintptr

palloc persistentAlloc // per-P to avoid mutex // 分配器,每个P一个,避免加锁

// Per-P GC state
// 每个P的GC状态
gcAssistTime int64 // Nanoseconds in assistAlloc // gcAassistAlloc中计时
gcFractionalMarkTime int64 // Nanoseconds in fractional mark worker // GC Mark计时
gcBgMarkWorker guintptr // 标记G
gcMarkWorkerMode gcMarkWorkerMode // 标记模式

// gcMarkWorkerStartTime is the nanotime() at which this mark
// worker started.
gcMarkWorkerStartTime int64 // mark worker启动时间

// 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.
// gcw是此P的GC工作缓冲区高速缓存。工作缓冲区由写屏障填充,由辅助mutator(赋值器)耗尽,并放置在某些GC状态转换上。
gcw gcWork // GC 的本地工作队列,灰色对象管理

// wbBuf is this P's GC write barrier buffer.
//
// TODO: Consider caching this in the running G.
// wbBuf 是当前 P 的 GC 的 write barrier 缓存
wbBuf wbBuf

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

pad cpu.CacheLinePad
}

gobuf

1
2
3
4
5
6
7
8
9
10
11
// src/runtime/runtime2.go
// gobuf记录与协程切换相关信息
type gobuf struct {
sp uintptr // sp 寄存器
pc uintptr // pc 寄存器
g guintptr // g 指针
ctxt unsafe.Pointer // 这个似乎是用来辅助 gc 的
ret sys.Uintreg // 作用 ? panic.go 中 recovery 函数有设置为 1
lr uintptr // 这是在 arm 上用的寄存器,不用关心
bp uintptr // for GOEXPERIMENT=framepointer // 开启 GOEXPERIMENT=framepointer ,才会有这个
}

M

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// src/runtime/runtime2.go
type m struct {
g0 *g // goroutine with scheduling stack // 用于执行调度指令的 goroutine, 用于调度的特殊 g , 调度和执行系统调用时会切换到这个 g
morebuf gobuf // gobuf arg to morestack // morestack 的 gobuf 参数
divmod uint32 // div/mod denominator for arm - known to liblink

// Fields not known to debuggers.
// debugger 不知道的字段
procid uint64 // for debuggers, but offset not hard-coded // 用于 debugger,偏移量不是写死的
gsignal *g // signal-handling g // 用于 debugger,偏移量不是写死的
goSigStack gsignalStack // Go-allocated signal handling stack // Go 分配的 signal handling 栈
sigmask sigset // storage for saved signal mask // 用于保存 saved signal mask
tls [6]uintptr // thread-local storage (for x86 extern register) // thread-local storage (对 x86 而言为额外的寄存器)
mstartfn func() // M启动函数
curg *g // current running goroutine // 当前运行的用户 g
caughtsig guintptr // goroutine running during fatal signal // goroutinefatal signal 中运行
p puintptr // attached p for executing go code (nil if not executing go code) // 执行 go 代码时持有的 p (如果没有执行则为 nil)
nextp puintptr // 下一个p, 唤醒 M 时, M 会拥有这个 P
oldp puintptr // the p that was attached before executing a syscall // 执行系统调用之前绑定的 p
id int64 // ID
mallocing int32 // 是否正在分配内存
throwing int32 // 是否正在抛出异常
preemptoff string // if != "", keep curg running on this m // 如果不为空串 "",继续让当前 g 运行在该 M
locks int32 // M的锁
dying int32 // 是否正在死亡,参见startpanic_m
profilehz int32 // cpu profiling rate
spinning bool // m is out of work and is actively looking for work // m 当前没有运行 work 且正处于寻找 work 的活跃状态
blocked bool // m is blocked on a note // m 阻塞在一个 note
inwb bool // m is executing a write barrier // m 在执行write barrier
newSigstack bool // minit on C thread called sigaltstack // C 线程上的 minit 是否调用了 signalstack
printlock int8 // print 锁,参见 print.go printlock/printunlock
incgo bool // m is executing a cgo call // m 正在执行 cgo 调用
freeWait uint32 // if == 0, safe to free g0 and delete m (atomic) // 如果为 0,安全的释放 g0 并删除 m (原子操作)
fastrand [2]uint32 // 快速随机
needextram bool // 需要额外的 m
traceback uint8 // 回溯
ncgocall uint64 // number of cgo calls in total // 总共的 cgo 调用数
ncgo int32 // number of cgo calls currently in progress // 正在进行的 cgo 调用数
cgoCallersUse uint32 // if non-zero, cgoCallers in use temporarily // 如果非零,则表示 cgoCaller 正在临时使用
cgoCallers *cgoCallers // cgo traceback if crashing in cgo call // cgo 调用崩溃的 cgo 回溯
park note // M 休眠时使用的信号量, 唤醒 M 时会通过它唤醒
alllink *m // on allm // 在 allm 上,将所有的 m 链接起来
schedlink muintptr // 下一个 m , 当 m 在链表结构中会使用
mcache *mcache // 分配内存时使用的本地分配器, 和 p.mcache 一样(拥有 P 时会复制过来)
lockedg guintptr // 表示与当前 M 锁定的那个 G 。运行时系统会把 一个 M 和一个 G 锁定,一旦锁定就只能双方相互作用,不接受第三者。g.lockedm 的对应值
createstack [32]uintptr // stack that created this thread.// 当前线程创建的栈
lockedExt uint32 // tracking for external LockOSThread // 外部 LockOSThread 追踪,LockOSThread/UnlockOSThread
lockedInt uint32 // tracking for internal lockOSThread // 内部 lockOSThread 追踪,lockOSThread/unlockOSThread
nextwaitm muintptr // next m waiting for lock // 内部 lockOSThread 追踪
waitunlockf unsafe.Pointer // todo go func(*g, unsafe.pointer) bool // 参见proc.go gopark
waitlock unsafe.Pointer // 参见proc.go gopark
waittraceev byte // 参见proc.go gopark
waittraceskip int // 参见proc.go gopark
startingtrace bool // 开始trace,见trace.go StartTrace
syscalltick uint32 // 每次系统调用时增加
thread uintptr // thread handle // 线程句柄
freelink *m // on sched.freem // 在 sched.freem

// these are here because they are too large to be on the stack
// of low-level NOSPLIT functions.
// 下面这些字段因为它们太大而不能放在低级的 NOSPLIT 函数的堆栈上。
libcall libcall // 调用信息
libcallpc uintptr // for cpu profiler // 用于 cpu profiler
libcallsp uintptr // libcall SP
libcallg guintptr // libcall G
syscall libcall // stores syscall parameters on windows // 存储 windows 上系统调用的参数

vdsoSP uintptr // SP for traceback while in VDSO call (0 if not in call)
vdsoPC uintptr // PC for traceback while in VDSO call

mOS
}

schedt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// src/runtime/runtime2.go
type schedt struct {
// accessed atomically. keep at top to ensure alignment on 32-bit systems.
// 原子访问,放到顶部,确保在32位系统上对齐(8字节)。
goidgen uint64 // go runtime ID生成器,原子自增,在newproc1和oneNewExtraM中使用了
lastpoll uint64 // 上一次轮询的时间(nanotime)

lock mutex // 锁

// When increasing nmidle, nmidlelocked, nmsys, or nmfreed, be
// sure to call checkdead().
// 当增加nmidle,nmidlelocked,nmsys或nmfreed时,请确保调用checkdead()。

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 // 当前等待工作的被 lock 的 M 计数
mnext int64 // number of m's that have been created and next M ID // 已经被创建的 M 的数量,下一个 M 的 ID
maxmcount int32 // maximum number of m's allowed (or die) // 最大M的数量
nmsys int32 // number of system m's not counted for deadlock // 系统 M 的数量, 在 deadlock 中不计数
nmfreed int64 // cumulative number of freed m's // 释放的 M 的累计数量

ngsys uint32 // number of system goroutines; updated atomically // 系统调用 goroutine 的数量,原子更新

pidle puintptr // idle p's // 空闲的P
npidle uint32 // 空闲的P的数目
nmspinning uint32 // See "Worker thread parking/unparking" comment in proc.go. // 处于spinning的M的数目

// Global runnable queue.
runq gQueue // 全局的 G 运行队列
runqsize int32 // 全局的 G 运行队列大小

// disable controls selective disabling of the scheduler.
//
// Use schedEnableUser to control this.
//
// disable is protected by sched.lock.
// disable控制选择性禁用调度程序。使用schedEnableUser进行控制。受sched.lock保护。
disable struct {
// user disables scheduling of user goroutines.
user bool // 用户禁用用户的goroutines调度
runnable gQueue // pending runnable Gs // 等待的可运行的G队列
n int32 // length of runnable // runnable的长度
}

// Global cache of dead G's.
// dead G全局缓存,已退出的 goroutine 对象缓存下来,避免每次创建 goroutine 时都重新分配内存,可参考proc.go中的gfput,gfget
gFree struct {
lock mutex // 锁
stack gList // Gs with stacks // 有堆栈的G
noStack gList // Gs without stacks // 没有堆栈的G
n int32 // stack和noStack中的总数
}

// Central cache of sudog structs.
sudoglock mutex // sudog缓存的锁
sudogcache *sudog // sudog缓存

// Central pool of available defer structs of different sizes.
deferlock mutex // defer缓存的锁
deferpool [5]*_defer // defer缓存

// freem is the list of m's waiting to be freed when their
// m.exited is set. Linked through m.freelink.
freem *m // freem是设置 m.exited 时等待释放的 m 列表。通过 m.freelink 链接。

gcwaiting uint32 // gc is waiting to run // GC等待运行
stopwait int32 // 需要停止P的数目
stopnote note // stopwait睡眠唤醒事件
sysmonwait uint32 // 等待系统监控
sysmonnote note // sysmonwait睡眠唤醒事件

// safepointFn should be called on each P at the next GC
// safepoint if p.runSafePointFn is set.
// 如果设置了p.runSafePointFn,则应在下一个GC安全点的每个P上调用safepointFn。
safePointFn func(*p) // 安全点函数
safePointWait int32 // 等待safePointFn执行
safePointNote note // safePointWait睡眠唤醒事件

profilehz int32 // cpu profiling rate // CPU

procresizetime int64 // nanotime() of last change to gomaxprocs // 上一次修改gomaxprocs的时间,参见proc.go中的procresize
totaltime int64 // ∫gomaxprocs dt up to procresizetime // 总时间
}

Global objects

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/runtime/runtime2.go
// 全局的一些对象
var (
allglen uintptr // 所有G的数目
allm *m // 所有的M
allp []*p // len(allp) == gomaxprocs; may change at safe points, otherwise immutable // 可能在安全区更改,否则不变
allpLock mutex // Protects P-less reads of allp and all writes // allp的锁
gomaxprocs int32 // GOMAXPROCS
ncpu int32 // CPU数目
forcegc forcegcstate // 强制GC
sched schedt // 调度
newprocs int32 // GOMAXPROCS函数设置,startTheWorld处理
)
1
2
3
4
5
// src/runtime/proc.go
var (
m0 m // 主M,asm_amd64.s中runtime·rt0_go初始化
g0 g // 主G,asm_amd64.s中runtime·rt0_go初始化
)

结束语

  这里主要介绍 GPM 的一些基础概念。下一篇,将介绍 GMP 的调度。

-------------本文结束感谢您的阅读-------------