Golang中GMP模型深入探究
GMP 模型是 Go 语言中的一种调度模型,用于管理 Go 程序中的并发执行。GMP 代表 Goroutines、M(OS 线程,Machine)和 P(Processor,逻辑处理器)。这种模型使得 Go 语言可以高效地调度和执行大量的 goroutine
GMP
G
- 即
goroutine,协程的抽象。 - g需要绑定到p才能执行。
M
- 即
machine,是golang中对线程的抽象 - m不直接执行g,而是先和p绑定,由其实现代理
P
- 即
processor,golang中的调度器 - p的数量决定了g最大的并发数量,可以通过
GOMAXPROCS进行设置。
G
数据结构
1type g struct {
2 // stack: 表示实际的堆栈内存区间:[stack.lo, stack.hi)。
3 stack stack
4
5 // ...
6
7 // 指向最里面的 panic 对象。
8 _panic *_panic
9
10 // 指向最里面的 defer 对象。
11 _defer *_defer
12
13 // 当前关联的 m 结构体。
14 m *m
15
16 // 保存 goroutine 调度相关的上下文。
17 sched gobuf
18
19 // 如果状态为 Gsyscall,则保存系统调用的堆栈指针以便 GC 使用。
20 syscallsp uintptr
21
22 // 如果状态为 Gsyscall,则保存系统调用的程序计数器以便 GC 使用。
23 syscallpc uintptr
24
25 // 堆栈顶部预期的堆栈指针,用于 traceback 检查。
26 stktopsp uintptr
27
28 // 通用指针参数字段,用于在特定上下文中传递值。
29 param unsafe.Pointer
30
31 // goroutine 的原子状态。
32 atomicstatus uint32
33
34 // : sigprof/scang 锁,用于同步访问堆栈。
35 stackLock uint32
36
37 // goroutine的唯一标识符。
38 goid int64
39
40 // 在调度队列中的链接。
41 schedlink guintptr
42
43 // goroutine 阻塞的近似时间。
44 waitsince int64
45
46 // 如果状态为 Gwaiting,则表示阻塞原因。
47 waitreason waitReason
48
49 // 抢占信号,类似于 stackguard0 = stackpreempt。
50 preempt bool
51
52 // 在抢占时转换为 _Gpreempted,否则只是取消调度。
53 preemptStop bool
54
55 // 在同步安全点缩小堆栈。
56 preemptShrink bool
57
58 // 表示 g 是否在异步安全点停止。
59 asyncSafePoint bool
60
61 // 在意外错误地址上触发 panic(而不是崩溃)。
62 paniconfault bool
63
64 // g 已扫描堆栈,受状态中的 _Gscan 位保护。
65 gcscandone bool
66
67 // 禁止拆分堆栈。
68 throwsplit bool
69
70 // 表示是否有未锁定的通道指向此 goroutine 的堆栈。
71 activeStackChans bool
72
73 // 表示 goroutine 是否即将停在 chansend 或 chanrecv 上。
74 parkingOnChan uint8
75
76 // 忽略竞争检测事件。
77 raceignore int8
78
79 // StartTrace 已发出关于此 goroutine 的 EvGoInSyscall 事件。
80 sysblocktraced bool
81
82 // 是否跟踪此 goroutine 的调度延迟统计。
83 tracking bool
84
85 // 用于决定是否跟踪此 goroutine。
86 trackingSeq uint8
87
88 // 此 goroutine 上次变为可运行状态的时间戳,仅在跟踪时使用。
89 runnableStamp int64
90
91 // 处于可运行状态的时间,在运行时清除,仅在跟踪时使用。
92 runnableTime int64
93
94 // 系统调用返回时的 CPU 时钟周期(用于跟踪)。
95 sysexitticks int64
96
97 // 跟踪事件序列器。
98 traceseq uint64
99
100 // 最后一个为此 goroutine 发出事件的 P。
101 tracelastp puintptr
102
103 // 当前锁定的 m 结构体指针。
104 lockedm muintptr
105
106 // 信号值。
107 sig uint32
108
109 // 写缓冲区。
110 writebuf []byte
111
112 // ...
113
114 // 信号发生时的程序计数器。
115 sigpc uintptr
116
117 // 创建此 goroutine 的 go 语句的程序计数器。
118 gopc uintptr
119
120 // 创建此 goroutine 的祖先信息(仅在 debug.tracebackancestors 时使用)。
121 ancestors *[]ancestorInfo
122
123 // goroutine 函数的起始程序计数器。
124 startpc uintptr
125
126 // 竞争检测上下文。
127 racectx uintptr
128
129 // g 正在等待的 sudog 结构(具有有效的 elem 指针)。
130 waiting *sudog
131
132 // cgo 回溯上下文。
133 cgoCtxt []uintptr
134
135 // 分析器标签。
136 labels unsafe.Pointer
137
138 // 缓存的计时器,用于 time.Sleep。
139 timer *timer
140
141 // 表示是否正在参与 select 操作以及是否有人赢得了竞争。
142 selectDone uint32
143
144 // ...
145}
g的生命周期
1const (
2 // G status
3
4 // 协程开始创建的状态,此时还未初始化完成
5 _Gidle = iota // 0
6 // 协程再待执行队列中,等待被执行
7 _Grunnable // 1
8 // 协程正在执行,同一时刻一个p中只能有一个g处于这个状态
9 _Grunning // 2
10 // 协程正在执行系统调用
11 _Gsyscall // 3
12 // 协程处于挂起状态,需要等待被唤醒。gc,channel通信,锁操作的时候会进入这个状态。
13 _Gwaiting // 4
14
15 _Gmoribund_unused // 5
16
17 // 协程刚初始化完成,或者已经被销毁的时候的状态
18 _Gdead // 6
19
20 _Genqueue_unused // 7
21 _Gcopystack // 8
22 _Gpreempted // 9
23 _Gscan = 0x1000
24 _Gscanrunnable = _Gscan + _Grunnable // 0x1001
25 _Gscanrunning = _Gscan + _Grunning // 0x1002
26 _Gscansyscall = _Gscan + _Gsyscall // 0x1003
27 _Gscanwaiting = _Gscan + _Gwaiting // 0x1004
28 _Gscanpreempted = _Gscan + _Gpreempted // 0x1009
29)
M
数据结构
1type m struct {
2 g0 *g // 调度堆栈上的 goroutine。
3
4 morebuf gobuf // 传递给 morestack 的 gobuf 参数。
5 // ...
6 procid uint64 // 用于调试器,但偏移量没有硬编码。
7 gsignal *g // 处理信号的 goroutine。
8 goSigStack gsignalStack // Go 分配的信号处理堆栈。
9 sigmask sigset // 保存的信号掩码。
10
11 tls [tlsSlots]uintptr // 线程本地存储(对于 x86 extern 寄存器)。
12
13 mstartfn func() // 启动函数。
14 curg *g // 当前运行的 goroutine。
15 caughtsig guintptr // 处理致命信号时正在运行的 goroutine。
16
17 p puintptr // 执行 Go 代码时附加的 P(如果不执行 Go 代码则为 nil)。
18
19 nextp puintptr // 下一个 P。
20 oldp puintptr // 执行系统调用前附加的 P。
21 id int64 // 线程 ID。
22 mallocing int32 // 正在进行的内存分配操作数。
23 throwing throwType // 抛出异常的类型。
24 preemptoff string // 如果不为空,保持当前 goroutine 在此 m 上运行。
25 locks int32 // 持有的锁的数量。
26 dying int32 // 正在终止的标志。
27 profilehz int32 // 性能分析的频率。
28 spinning bool // m 是否无工作并积极寻找工作。
29 blocked bool // m 是否在 note 上阻塞。
30 newSigstack bool // 在 C 线程上调用 sigaltstack 的 minit 标志。
31 printlock int8 // 打印锁。
32 incgo bool // m 是否正在执行 cgo 调用。
33 freeWait uint32 // 如果为 0,则安全释放 g0 并删除 m(原子操作)。
34 fastrand uint64 // 快速随机数种子。
35 needextram bool // 是否需要额外的 m。
36 traceback uint8 // 是否进行回溯。
37 ncgocall uint64 // 总的 cgo 调用次数。
38 ncgo int32 // 当前正在进行的 cgo 调用次数。
39 cgoCallersUse uint32 // 如果非零,则 cgoCallers 临时使用。
40 cgoCallers *cgoCallers // 如果在 cgo 调用中崩溃,则保存 cgo 回溯。
41 park note // 用于 goroutine 的 park。
42 alllink *m // 在 allm 链表上。
43 schedlink muintptr // 在调度链表上。
44 lockedg guintptr // 当前锁定的 goroutine。
45 createstack [32]uintptr // 创建此线程的堆栈。
46 lockedExt uint32 // 跟踪外部 LockOSThread 的状态。
47 lockedInt uint32 // 跟踪内部 lockOSThread 的状态。
48 nextwaitm muintptr // 等待锁的下一个 m。
49 waitunlockf func(*g, unsafe.Pointer) bool // 等待解锁的函数。
50 waitlock unsafe.Pointer // 等待解锁的指针。
51 waittraceev byte // 等待解锁的跟踪事件。
52 waittraceskip int // 等待解锁的跟踪跳过数。
53 startingtrace bool // 开始跟踪的标志。
54 syscalltick uint32 // 系统调用的时钟周期。
55 freelink *m // 在 sched.freem 链表上。
56 libcall libcall // 用于低级别 NOSPLIT 函数的参数。
57 libcallpc uintptr // 用于 CPU 性能分析的程序计数器。
58 libcallsp uintptr // 用于 CPU 性能分析的堆栈指针。
59 libcallg guintptr // 用于 CPU 性能分析的 goroutine。
60 syscall libcall // 存储 Windows 上的系统调用参数。
61 vdsoSP uintptr // 在 VDSO 调用中进行回溯时的堆栈指针(如果不在调用中则为 0)。
62 vdsoPC uintptr // 在 VDSO 调用中进行回溯时的程序计数器。
63 preemptGen uint32 // 记录完成的抢占信号次数,用于检测请求抢占但失败的情况,原子访问。
64 signalPending uint32 // 是否有待处理的抢占信号,原子访问。
65 dlogPerM // 用于调试日志的字段。
66 mOS // 操作系统相关字段。
67 locksHeldLen int // 此 m 持有的锁的数量(最多 10 个),由锁排序代码维护。
68 // ...
69}
- g0是一类特殊的goroutine,不用与执行用户方法,负责执行g之间的切换调度,与m的关系比例为1:1
P
数据类型
1type p struct {
2 id int32 // 处理器的唯一标识符
3 status uint32 // 处理器的当前状态,比如 idle、running 等
4 link puintptr // 链接到下一个处理器,用于维护处理器链表
5 schedtick uint32 // 调度器调用计数器,每次调度调用时递增
6 syscalltick uint32 // 系统调用计数器,每次系统调用时递增
7 sysmontick sysmontick // sysmon(系统监控器)最后一次观察到的tick值
8 m muintptr // 关联的 OS 线程(m 结构体),如果空闲则为 nil
9 mcache *mcache // 内存分配缓存
10 pcache pageCache // 页面缓存
11 raceprocctx uintptr // 用于数据竞争检测的上下文
12
13 deferpool []*_defer // 可用的 defer 结构体池
14 deferpoolbuf [32]*_defer // 缓存的 defer 结构体
15
16 goidcache uint64 // goroutine ID 缓存,减少对全局 ID 生成器的访问
17 goidcacheend uint64 // goroutine ID 缓存的结束位置
18
19 runqhead uint32 // 可运行 goroutine 队列的头部索引
20 runqtail uint32 // 可运行 goroutine 队列的尾部索引
21 runq [256]guintptr // 可运行 goroutine 队列
22 runnext guintptr // 下一个要运行的 goroutine,如果不为空,则在当前 goroutine 运行完后立即运行
23
24 gFree struct { // 可用 goroutine 列表(状态为 Gdead)
25 gList
26 n int32
27 }
28
29 sudogcache []*sudog // sudog 结构体缓存
30 sudogbuf [128]*sudog // 缓存的 sudog 结构体
31
32 mspancache struct { // mspan 对象缓存
33 len int // 缓存的长度
34 buf [128]*mspan // 缓存的 mspan 对象
35 }
36
37 tracebuf traceBufPtr // 跟踪缓冲区
38
39 traceSweep bool // 是否跟踪 sweep 事件
40 traceSwept, traceReclaimed uintptr // 跟踪当前 sweep 循环中被 sweep 和回收的字节数
41
42 palloc persistentAlloc // 持久化分配器
43
44 _ uint32 // 对齐字段,确保后面的字段是原子的
45
46 timer0When uint64 // 定时器堆中第一个条目的时间
47 timerModifiedEarliest uint64 // 最早的修改过的定时器的时间
48
49 gcAssistTime int64 // 在 assistAlloc 中花费的纳秒数
50 gcFractionalMarkTime int64 // 在 fractional mark worker 中花费的纳秒数
51
52 limiterEvent limiterEvent // GC CPU 限制器事件
53
54 gcMarkWorkerMode gcMarkWorkerMode // 下一个 mark worker 的模式
55 gcMarkWorkerStartTime int64 // 最近一个 mark worker 开始的时间
56
57 gcw gcWork // 当前处理器的 GC 工作缓冲区
58 wbBuf wbBuf // 当前处理器的写屏障缓冲区
59
60 runSafePointFn uint32 // 如果为 1,在下一个安全点运行 sched.safePointFn
61
62 statsSeq uint32 // 统计序列计数器,偶数表示当前没有写入统计,奇数表示正在写入统计
63
64 timersLock mutex // 定时器锁
65 timers []*timer // 定时器数组,必须持有 timersLock 访问
66 numTimers uint32 // 定时器堆中的定时器数量(使用原子操作修改)
67 deletedTimers uint32 // 定时器堆中的已删除定时器数量(使用原子操作修改)
68
69 timerRaceCtx uintptr // 执行定时器函数时使用的竞争上下文
70
71 maxStackScanDelta int64 // 存活 goroutine 持有的堆栈空间的累计量
72 scannedStackSize uint64 // 当前 goroutine 的堆栈大小
73 scannedStacks uint64 // 当前 goroutine 的数量
74
75 preempt bool // 如果设置为 true,表示这个处理器应尽快进入调度器
76}
Schedt
全局的runq队列
数据结构
1type schedt struct {
2
3 goidgen uint64 // 全局唯一的 goroutine ID 生成器。
4 lastpoll uint64 // 上次网络轮询的时间,如果当前正在轮询则为 0。
5 pollUntil uint64 // 当前轮询睡眠到的时间。
6
7 lock mutex // 调度器的全局锁。
8
9 midle muintptr // 空闲的 M 列表,等待工作。
10 nmidle int32 // 等待工作的空闲 M 的数量。
11 nmidlelocked int32 // 等待工作的锁定 M 的数量。
12 mnext int64 // 已创建的 M 的数量和下一个 M ID。
13 maxmcount int32 // 允许的最大 M 数量(或死锁)。
14 nmsys int32 // 不计入死锁检测的系统 M 的数量。
15 nmfreed int64 // 累计释放的 M 的数量。
16
17 ngsys uint32 // 系统 goroutines 的数量,原子更新。
18
19 pidle puintptr // 空闲的 P 列表。
20 npidle uint32 // 空闲 P 的数量。
21 nmspinning uint32 // 查看 proc.go 中的 "Worker thread parking/unparking" 注释。
22
23 runq gQueue // 全局可运行的 G 队列。
24 runqsize int32 // 全局可运行队列中的 G 数量。
25
26 disable struct {
27 // user disables scheduling of user goroutines.
28 user bool // 用户禁用调度用户 goroutine。
29 runnable gQueue // 待处理的可运行 G 队列。
30 n int32 // 待处理的可运行 G 数量。
31 }
32
33 gFree struct {
34 lock mutex // 锁保护 gFree 访问。
35 stack gList // 有堆栈的 G 列表。
36 noStack gList // 无堆栈的 G 列表。
37 n int32 // 总数。
38 }
39
40 sudoglock mutex // 锁保护 sudogcache 访问。
41 sudogcache *sudog // 可用 sudog 结构的中央缓存。
42
43 deferlock mutex // 锁保护 deferpool 访问。
44 deferpool *_defer // 可用 defer 结构的中央池。
45
46 freem *m // 等待被释放的 M 列表。
47
48 gcwaiting uint32 // GC 等待运行的标志。
49 stopwait int32 // 停止等待计数器。
50 stopnote note // 停止等待通知。
51 sysmonwait uint32 // 系统监视器等待标志。
52 sysmonnote note // 系统监视器等待通知。
53
54 safePointFn func(*p) // 在下一个 GC 安全点时在每个 P 上调用的函数。
55 safePointWait int32 // 安全点等待计数器。
56 safePointNote note // 安全点等待通知。
57
58 profilehz int32 // CPU 性能分析率。
59
60 procresizetime int64 // 上次 gomaxprocs 变更的时间(纳秒)。
61 totaltime int64 // ∫gomaxprocs dt 到 procresizetime。
62
63 sysmonlock mutex // 保护 sysmon 操作运行时的锁。
64
65 timeToRun timeHistogram // 调度延迟的分布,定义为 G 在 _Grunnable 状态中花费的时间总和。
66}
- runq:全局goroutine队列
- runqsize:全局goroutine队列的容量
调度流程
调度指的是g0按照特定的策略找到下一个可执行的g的过程。
两种g的切换
func gogo():由g0切换到gfunc m_call():由g切换为g0
调度类型
主动调度
被动调度
正常调度
抢占调度
找到可执行的g
findRunnable()
获取可调度的g。
该部分代码主要在runtime/proc.go#findRunnable()的方法中
1func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
2 _g_ := getg()
3
4 // 这里和handoffp中的条件必须一致:
5 // 如果findrunnable将返回一个要运行的G,handoffp必须启动一个M。
6
7top:
8 // ...
9
10 // 偶尔检查全局可运行队列,以确保公平性。
11 // 否则,两个goroutine可以通过不断重启彼此来完全占据本地运行队列。
12 if _p_.schedtick%61 == 0 && sched.runqsize > 0 {
13 lock(&sched.lock)
14
15 // 如果已经在本地队列中check了61次了,则这次从全局队列中查找
16 gp = globrunqget(_p_, 1)
17
18 unlock(&sched.lock)
19 if gp != nil {
20 return gp, false, false
21 }
22 }
23
24 // 从本地队列中获取一个可运行的g
25 if gp, inheritTime := runqget(_p_); gp != nil {
26 return gp, inheritTime, false
27 }
28
29 // 如果本地队列中没有获取到,则从全局runq中获取一个可运行的g
30 if sched.runqsize != 0 {
31 lock(&sched.lock)
32 gp := globrunqget(_p_, 0)
33 unlock(&sched.lock)
34 if gp != nil {
35 return gp, false, false
36 }
37 }
38
39 // 查找并唤醒一些io协程
40 if netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Load64(&sched.lastpoll) != 0 {
41 if list := netpoll(0); !list.empty() { // non-blocking
42 gp := list.pop()
43 injectglist(&list)
44 casgstatus(gp, _Gwaiting, _Grunnable)
45 if trace.enabled {
46 traceGoUnpark(gp, 0)
47 }
48 return gp, false, false
49 }
50 }
51
52 // Spinning Ms: steal work from other Ps.
53 //
54 // Limit the number of spinning Ms to half the number of busy Ps.
55 // This is necessary to prevent excessive CPU consumption when
56 // GOMAXPROCS>>1 but the program parallelism is low.
57 procs := uint32(gomaxprocs)
58 if _g_.m.spinning || 2*atomic.Load(&sched.nmspinning) < procs-atomic.Load(&sched.npidle) {
59 if !_g_.m.spinning {
60 _g_.m.spinning = true
61 atomic.Xadd(&sched.nmspinning, 1)
62 }
63
64 // work-stealing操作,尝试从别的p中的本地队列中窃取别的p的一半的g过来。
65 gp, inheritTime, tnow, w, newWork := stealWork(now)
66 now = tnow
67 if gp != nil {
68 // Successfully stole.
69 return gp, inheritTime, false
70 }
71 if newWork {
72 // There may be new timer or GC work; restart to
73 // discover.
74 goto top
75 }
76 if w != 0 && (pollUntil == 0 || w < pollUntil) {
77 // Earlier timer to wait for.
78 pollUntil = w
79 }
80 }
81
82 // ...
83
84}
stealWork()
1func stealWork(now int64) (gp *g, inheritTime bool, rnow, pollUntil int64, newWork bool) {
2 pp := getg().m.p.ptr()
3
4 ranTimer := false
5
6 // 最多发起4次尝试
7 const stealTries = 4
8 for i := 0; i < stealTries; i++ {
9 stealTimersOrRunNextG := i == stealTries-1
10
11 // fastrand():保证是随机挑选的p
12 for enum := stealOrder.start(fastrand()); !enum.done(); enum.next() {
13 if sched.gcwaiting != 0 {
14 return nil, false, now, pollUntil, true
15 }
16 p2 := allp[enum.position()]
17 if pp == p2 {
18 continue
19 }
20
21
22 if stealTimersOrRunNextG && timerpMask.read(enum.position()) {
23 tnow, w, ran := checkTimers(p2, now)
24 now = tnow
25 if w != 0 && (pollUntil == 0 || w < pollUntil) {
26 pollUntil = w
27 }
28 if ran {
29
30 if gp, inheritTime := runqget(pp); gp != nil {
31 return gp, inheritTime, now, pollUntil, ranTimer
32 }
33 ranTimer = true
34 }
35 }
36
37
38 if !idlepMask.read(enum.position()) {
39 // 这里会窃取一半的g
40 if gp := runqsteal(pp, p2, stealTimersOrRunNextG); gp != nil {
41 return gp, false, now, pollUntil, ranTimer
42 }
43 }
44 }
45 }
46
47
48 return nil, false, now, pollUntil, ranTimer
49}
50
51
52
53func runqgrab(_p_ *p, batch *[256]guintptr, batchHead uint32, stealRunNextG bool) uint32 {
54 for {
55 h := atomic.LoadAcq(&_p_.runqhead) // load-acquire, synchronize with other consumers
56 t := atomic.LoadAcq(&_p_.runqtail) // load-acquire, synchronize with the producer
57 n := t - h
58 n = n - n/2
59 if n == 0 {
60 if stealRunNextG {
61 // Try to steal from _p_.runnext.
62 if next := _p_.runnext; next != 0 {
63 if _p_.status == _Prunning {
64 if GOOS != "windows" && GOOS != "openbsd" && GOOS != "netbsd" {
65 usleep(3)
66 } else {
67
68 osyield()
69 }
70 }
71 if !_p_.runnext.cas(next, 0) {
72 continue
73 }
74 batch[batchHead%uint32(len(batch))] = next
75 return 1
76 }
77 }
78 return 0
79 }
80 if n > uint32(len(_p_.runq)/2) { // read inconsistent h and t
81 continue
82 }
83 for i := uint32(0); i < n; i++ {
84 g := _p_.runq[(h+i)%uint32(len(_p_.runq))]
85 batch[(batchHead+i)%uint32(len(batch))] = g
86 }
87 if atomic.CasRel(&_p_.runqhead, h, h+n) { // cas-release, commits consume
88 return n
89 }
90 }
91}
work-stealing:当p无法从给自己的本地队列和全局队列中获取可运行的g的时候,就会尝试从别的p窃取一半的g到自己的本地队列。
执行g
execute()
1func execute(gp *g, inheritTime bool) {
2 _g_ := getg()
3
4 // ...
5
6 _g_.m.curg = gp
7 gp.m = _g_.m
8
9 // 使用cas将g的状态从runnable改成running
10 casgstatus(gp, _Grunnable, _Grunning)
11 gp.waitsince = 0
12 gp.preempt = false
13 gp.stackguard0 = gp.stack.lo + _StackGuard
14 if !inheritTime {
15 _g_.m.p.ptr().schedtick++
16 }
17
18 // ...
19
20
21 // 执行了gogo()方法之后,执行权将由g0切换到g
22 gogo(&gp.sched)
23}
调度中常见的方法
Gosched
该方法执行后,执行全将由g切换回g0
1func Gosched() {
2 checkTimeouts()
3
4 // call了gosched_m这个方法
5 mcall(gosched_m)
6}
7
8
9func gosched_m(gp *g) {
10 // ...
11 goschedImpl(gp)
12}
13
14
15func goschedImpl(gp *g) {
16 status := readgstatus(gp)
17 if status&^_Gscan != _Grunning {
18 dumpgstatus(gp)
19 throw("bad g status")
20 }
21
22 // 将当前运行的g的状态从running改为runnable
23 casgstatus(gp, _Grunning, _Grunnable)
24
25 // 解绑g和m
26 dropg()
27
28 // 加锁,并将当前的g放入全局队列中
29 lock(&sched.lock)
30 globrunqput(gp)
31 unlock(&sched.lock)
32
33 schedule()
34}
gopark
执行权将从g切换到g0
1func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
2 if reason != waitReasonSleep {
3 checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
4 }
5 mp := acquirem()
6 gp := mp.curg
7 status := readgstatus(gp)
8
9 // 检查当前g的运行状态是否正确
10 if status != _Grunning && status != _Gscanrunning {
11 throw("gopark: bad g status")
12 }
13
14 // 保存一些状态
15 mp.waitlock = lock
16 mp.waitunlockf = unlockf
17 gp.waitreason = reason
18 mp.waittraceev = traceEv
19 mp.waittraceskip = traceskip
20 releasem(mp)
21
22 // 调用park_m
23 mcall(park_m)
24}
25
26
27
28func park_m(gp *g) {
29 _g_ := getg()
30
31 // ...
32
33 // 将当前正在执行的g的状态从running改成waiting
34 casgstatus(gp, _Grunning, _Gwaiting)
35
36 // 将g和m解绑
37 dropg()
38
39 if fn := _g_.m.waitunlockf; fn != nil {
40 ok := fn(gp, _g_.m.waitlock)
41 _g_.m.waitunlockf = nil
42 _g_.m.waitlock = nil
43 if !ok {
44 // ...
45
46 casgstatus(gp, _Gwaiting, _Grunnable)
47 execute(gp, true) // Schedule it back, never returns.
48 }
49 }
50
51 // 进入新的一轮调度流程
52 schedule()
53}
从上面的代码可以看出,在g进入了waiting状态的时候,这个g没有进入本地队列,也没有进入全局队列,那么该如何唤醒这个在waiting状态的g呢?
— END —