题目序号:(2807) 题目来源: 陌陌 频次: 1
# 答案: 小强
G、P、M 是 Go 调度器的三个核心组件,各司其职。在它们精密地配合下,Go 调度器得以高效运转,这也是Go天然支持高并发的内在动力。今天这篇文章我们来深入理解 GPM 模型。
先看 G,取 goroutine 的首字母,主要保存 goroutine 的一些状态信息以及 CPU 的一些寄存器的值,例如 IP 寄存器,以便在轮到本 goroutine 执行时,CPU 知道要从哪一条指令处开始执行。 runtime.g 的源码如下:
type g struct {
// goroutine 使用的栈
stack stack
// offset known to runtime/cgo
// 用于栈的扩张和收缩检查,抢占标志
stackguard0 uintptr
// offset known to liblink
stackguard1 uintptr
// offset known to liblink
_panic *_panic
// innermost panic - offset known to liblink
_defer *_defer
// 当前与 g 绑定的 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
// wakeup 时传入的参数
param unsafe.Pointer
// passed parameter on wakeup
atomicstatus uint32
stackLock uint32
// sigprof/scang lock;
goid int64
// g 被阻塞之后的近似时间
waitsince int64
// approx time when the g become blocked
// g 被阻塞的原因
waitreason string
// 指向全局队列里下一个 g
schedlink guintptr
// 抢占调度标志。这个为 true 时,stackguard0 等于 stackpreempt
preempt bool
// preemption signal, duplicates stackguard0 = stackpreempt
paniconfault bool
// panic (instead of crash) on unexpected fault address
preemptscan bool
// preempted g does scan for gc
gcscandone bool
// g has scanned stack; protected by _Gscan bit in status
gcscanvalid bool
// false at start of gc cycle, true if G has not run since last scan;
throwsplit bool
// must not split stack
raceignore int8
// ignore race detection events
sysblocktraced bool
// StartTrace has emitted EvGoInSyscall about this goroutine
// syscall 返回之后的 cputicks,用来做 tracing
sysexitticks int64
// cputicks when syscall has returned (for tracing)
traceseq uint64
// trace event sequencer
tracelastp puintptr
// last P emitted an event for this goroutine
// 如果调用了 LockOsThread,那么这个 g 会绑定到某个 m 上
lockedm *m
sig uint32
writebuf []byte
sigcode0 uintptr
sigcode1 uintptr
sigpc uintptr
// 创建该 goroutine 的语句的指令地址
gopc uintptr
// pc of go statement that created this goroutine
// goroutine 函数的指令地址
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
// cached timer for time.Sleep
gcAssistBytes int64
}
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
再来看 M,取 machine 的首字母,它代表一个工作线程,或者说系统线程。G 需要调度到 M 上才能运行,M 是真正工作的人。结构体 m 就是我们常说的 M,它保存了 M 自身使用的栈信息、当前正在 M 上执行的 G 信息、与之绑定的 P 信息……
当 M 没有工作可做的时候,在它休眠前,会“自旋”地来找工作:检查全局队列,查看 network poller,试图执行 gc 任务,或者“偷”工作。
结构体 m 的源码如下:
type m struct {
// 记录工作线程(也就是内核线程)使用的栈信息。在执行调度代码时需要使用
// 执行用户 goroutine 代码时,使用用户 goroutine 自己的栈,因此调度时会发生栈的切换
g0 *g
// goroutine with scheduling stack/
morebuf gobuf
// gobuf arg to morestack
divmod uint32
// div/mod denominator for arm - known to liblink
// Fields not known to debuggers.
procid uint64
// for debuggers, but offset not hard-coded
gsignal *g
// signal-handling g
sigmask sigset
// storage for saved signal mask
// 通过 tls 结构体实现 m 与工作线程的绑定
// 这里是线程本地存储
tls [6]uintptr
// thread-local storage (for x86 extern register)
mstartfn func()
// 指向正在运行的 gorutine 对象
curg *g
// current running goroutine
caughtsig guintptr
// goroutine running during fatal signal
// 当前工作线程绑定的 p
p puintptr
// attached p for executing go code (nil if not executing go code)
nextp puintptr
id int32
mallocing int32
throwing int32
// 该字段不等于空字符串的话,要保持 curg 始终在这个 m 上运行
preemptoff string
// if != "", keep curg running on this m
locks int32
softfloat int32
dying int32
profilehz int32
helpgc int32
// 为 true 时表示当前 m 处于自旋状态,正在从其他线程偷工作
spinning bool
// m is out of work and is actively looking for work
// m 正阻塞在 note 上
blocked bool
// m is blocked on a note
// m 正在执行 write barrier
inwb bool
// m is executing a write barrier
newSigstack bool
// minit on C thread called sigaltstack
printlock int8
// 正在执行 cgo 调用
incgo bool
// m is executing a cgo call
fastrand uint32
// cgo 调用总计数
ncgocall uint64
// number of cgo calls in total
ncgo int32
// number of cgo calls currently in progress
cgoCallersUse uint32
// if non-zero, cgoCallers in use temporarily
cgoCallers *cgoCallers
// cgo traceback if crashing in cgo call
// 没有 goroutine 需要运行时,工作线程睡眠在这个 park 成员上,
// 其它线程通过这个 park 唤醒该工作线程
park note
// 记录所有工作线程的链表
alllink *m
// on allm
schedlink muintptr
mcache *mcache
lockedg *g
createstack [32]uintptr
// stack that created this thread.
freglo [16]uint32
// d[i] lsb and f[i]
freghi [16]uint32
// d[i] msb and f[i+16]
fflag uint32
// floating point compare flags
locked uint32
// tracking for lockosthread
// 正在等待锁的下一个 m
nextwaitm uintptr
// next m waiting for lock
needextram bool
traceback uint8
waitunlockf unsafe.Pointer
// todo go func(*g, unsafe.pointer) bool
waitlock unsafe.Pointer
waittraceev byte
waittraceskip int
startingtrace bool
syscalltick uint32
// 工作线程 id
thread uintptr
// thread handle
// 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
mOS
}
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
再来看 P,取 processor 的首字母,为 M 的执行提供“上下文”,保存 M 执行 G 时的一些资源,例如本地可运行 G 队列,memeory cache 等。
一个 M 只有绑定 P 才能执行 goroutine,当 M 被阻塞时,整个 P 会被传递给其他 M ,或者说整个 P 被接管。
// p 保存 go 运行时所必须的资源
type p struct {
lock mutex
// 在 allp 中的索引
id int32
status uint32
// one of pidle/prunning/...
link puintptr
// 每次调用 schedule 时会加一
schedtick uint32
// 每次系统调用时加一
syscalltick uint32
// 用于 sysmon 线程记录被监控 p 的系统调用时间和运行时间
sysmontick sysmontick
// last tick observed by sysmon
// 指向绑定的 m,如果 p 是 idle 的话,那这个指针是 nil
m muintptr
// back-link to associated m (nil if idle)
mcache *mcache
racectx uintptr
deferpool [5][]*_defer
// pool of available defer structs of different sizes (see panic.go)
deferpoolbuf [5][32]*_defer
// Cache of goroutine ids, amortizes accesses to runtime·sched.goidgen.
goidcache uint64
goidcacheend uint64
// Queue of runnable goroutines. Accessed without lock.
// 本地可运行的队列,不用通过锁即可访问
runqhead uint32
// 队列头
runqtail uint32
// 队列尾
// 使用数组实现的循环队列
runq [256]guintptr
// runnext 非空时,代表的是一个 runnable 状态的 G,
// 这个 G 被 当前 G 修改为 ready 状态,相比 runq 中的 G 有更高的优先级。
// 如果当前 G 还有剩余的可用时间,那么就应该运行这个 G
// 运行之后,该 G 会继承当前 G 的剩余时间
runnext guintptr
// Available G's (status == Gdead)
// 空闲的 g
gfree *g
gfreecnt int32
sudogcache []*sudog
sudogbuf [128]*sudog
tracebuf traceBufPtr
traceSwept, traceReclaimed uintptr
palloc persistentAlloc
// per-P to avoid mutex
// Per-P GC state
gcAssistTime int64
// Nanoseconds in assistAlloc
gcBgMarkWorker guintptr
gcMarkWorkerMode gcMarkWorkerMode
runSafePointFn uint32
// if 1, run sched.safePointFn at next safe point
pad [sys.CacheLineSize]byte
}
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
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