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切换到g
  • func 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 —