go语言调度gmp数据结构
- g 表示goroutine,它是待执行的任务
- m 表示操作系统的线程,它由操作系统的调度器调度和管理
- p 表示处理器,可以把它看作在线程上运行的本地调度器
G
goroutine是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 // offset known to runtime/cgo
stackguard0 uintptr // offset known to liblink
stackguard1 uintptr // offset known to liblink
}
stack字段描述了当前goroutine的栈内存范围[stack.lo,stack.hi],stack
guard0可以用于调度器抢占式调度,除stackguard0外,goroutine中还包含另外3个与抢占密切相关的字段
type g struct {
preempt bool // preemption signal, duplicates stackguard0 = stackpreempt
preemptStop bool // transition to _Gpreempted on preemption; otherwise, just deschedule
preemptShrink bool // shrink stack at synchronous safe point
}
每个goroutine上都持有两个分别存储defer和panic对应结构体的链表
type g struct {
_panic *_panic // innermost panic - offset known to liblink
_defer *_defer // innermost defer
}
还有几个比较重要的字段
type g struct {
m *m // current m; offset known to arm liblink
sched gobuf
atomicstatus atomic.Uint32
}
- m——当前goroutine占用的线程,可能为空
- sched——存储goroutine的调度相关数据
- atomicstatus——goroutine的状态
type gobuf struct {
// The offsets of sp, pc, and g are known to (hard-coded in) libmach.
//
// ctxt is unusual with respect to GC: it may be a
// heap-allocated funcval, so GC needs to track it, but it
// needs to be set and cleared from assembly, where it's
// difficult to have write barriers. However, ctxt is really a
// saved, live register, and we only ever exchange it between
// the real register and the gobuf. Hence, we treat it as a
// root during stack scanning, which means assembly that saves
// and restores it doesn't need write barriers. It's still
// typed as a pointer so that any other writes from Go get
// write barriers.
sp uintptr
pc uintptr
g guintptr
ctxt unsafe.Pointer
ret uintptr
lr uintptr
bp uintptr // for framepointer-enabled architectures
}
- sp——栈指针
- pc——程序计数器
- g——持有runtime.gobuf的goroutine
- ret——系统调用的返回值
这些内容会在调度器保存或者恢复上下文时用到,其中的栈指针和程序计数器用来存储或者恢复寄存器中的值,改变程序即将执行的代码
M
go语言并发模型中的M是操作系统线程。调度器最多可以创建10000个线程,但是其中大多数线程不会执行用户代码(可能陷入系统调用),最多只会有GOMAXPROCS个活跃线程能够正常运行。
默认情况下,运行时会将GOMAXPROCS设置成当前机器的核数,我们也可以在程序中使用runtime.GOMAXPROCS来改变最大的活跃线程数。
默认情况下,一个四核机器会创建四个活跃的操作系统线程,每一个线程都对应一个运行时中的runtime.m结构体
在大多数情况下,我们会使用go语言的默认设置,也就是线程数等于cpu数,默认设置不会频繁触发操作系统的线程调度和上下文切换,所有调度都发生在用户态,由go语言调度器触发,能够减少很多额外开销。
go语言会使用私有结构体runtime.m表示操作系统线程
与goroutine相关的字段
type m struct {
g0 *g // goroutine with scheduling stack
curg *g // current running goroutine
}
其中g0是持有调度栈的goroutine,curg是在当前线程上运行的用户goroutine,他们也是操作系统线程仅仅关心的两个goroutine
g0是运行时中比较特殊的一个goroutine,它会深度参与运行时的调度过程,包括goroutine的创建、大内存分配和cgo函数的执行
runtime.m结构体中还存在3个与处理器相关的字段,它们分别表示正在运行代码的处理器p、暂存的处理器nextp和执行系统调用之前使用线程的处理器oldp
type m struct {
p puintptr // attached p for executing go code (nil if not executing go code)
nextp puintptr
oldp puintptr // the p that was attached before executing a syscall
}
除了上面介绍的字段外,runtime.m还包含大量与线程状态、锁、调度、系统调用有关的字段
P
调度器中的处理器p是线程和goroutine的中间层,它能提供线程需要的上下文,也会负责调度线程上的等待队列。通过处理器p的调度,每一个内核线程都能够执行多个goroutine,它能在goroutine进行一些I/O操作时及时让出计算资源,提高线程的利用率。
因为调度器在启动时就会创建GOMAXPROCS个处理器,所以go语言程序的处理器数量一定会等于GOMAXPROCS,这些处理器会绑定到不同的内核线程上。
runtime.p是处理器的运行时表示,作为调度器的内部实现,它包含的字段非常多,其中包括与性能追踪、垃圾收集和计时器相关的字段。这些字段非常重要,但是这里就不展示了,我们主要关注处理器中的线程和运行队列
type p struct {
m muintptr // back-link to associated m (nil if idle)
// Queue of runnable goroutines. Accessed without lock.
runqhead uint32
runqtail uint32
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.
//
// Note that while other P's may atomically CAS this to zero,
// only the owner P can CAS it to a valid G.
runnext guintptr
}
反向存储的线程维护着线程与处理器之间的关系,而runhead、runqtail和runq三个字段表示处理器持有的运行队列,其中存储着待执行的goroutine列表,runnext中是线程下一个需要执行的goroutine
处理器的状态:
- _Pidle 处理器没有运行用户代码或者调度器,被空闲队列或者改变其状态的结构持有,运行队列为空
- _Prunning 被线程m持有,并且正在执行用户代码或者调度器
- _Psyscall 没有执行用户代码,当前线程陷入系统调用
- _Pgcstop 被线程m持有,当前处理器由于垃圾收集被停止
- _Pdead 当前处理器已经停用