前言
做为Go开发者基本上对GMP已经很熟悉,这是Go的核心内容,三个核心部分共同配合下让Go 调度器得以高效运转。结合之前我们对编译和启动流程的总结,现在就更容易从结构和汇编调用的实际函数来进行结合理解,我们先来看Go调度器的组成部分GMP各部分的结构和用处。
注:文中GMP的底层数据结构都在src/runtime/runtime2.go中,每个结构体的字段数比较多,只截取了一部分进行了说明。
G(Goroutine)
G 取自Goroutine的首字母,一个G代表一个Goroutine,Goroutine是一个与其他Goroutines并行运行在同一地址空间的go函数或方法。
它是一个数据结构,这个结构体里面有栈信息,上下文等。它占用了更小的内存空间(2KB内存),同时也降低了上下文切换的开销,Goroutine数量理论上只受内存大小限制。
在使用go 关键字创建一个goroutine时候,会调用runtime.newproc来创建一个Goroutine,在我们将Go程序的启动流程的时候就清楚,它的汇编代码如下,调用的是src/runtime/proc.go的newproc()函数,newproc 方法会切换到调用 newproc1 函数进行 G 的创建。newproc1 方法很长,里面主要是获取 G ,然后对获取到的 G 做一些初始化的工作。
代码语言:javascript复制// asm_amd64.s
CALL runtime·newproc(SB)
//proc.go
func newproc(siz int32, fn *funcval) {
argp := add(unsafe.Pointer(&fn), sys.PtrSize)
gp := getg() //返回g实际的结构体指针
pc := getcallerpc()
systemstack(func() {
newg := newproc1(fn, argp, siz, gp, pc)
_p_ := getg().m.p.ptr()
runqput(_p_, newg, true)
if mainStarted {
wakep()
}
})
}
我们看g结构体的源码部分,结构体包含了g使用的栈,可能的panic和defer链、以及运行现场等信息。
代码语言:javascript复制type g struct {
stack stack // g 使用的栈
stackguard0 uintptr // offset known to liblink
stackguard1 uintptr // offset known to liblink
_panic *_panic // panic链表
_defer *_defer // defer延迟函数链表
m *m // 当前绑定的m
sched gobuf
// 其他运行现场信息
...
}
M(Machine)
M是一个线程或称为Machine(取首字母),G 需要调度到 M 上才能运行,M 是真正的执行者,调度器最多可以创建 10000 个线程。
最多只会有 GOMAXPROCS 个活跃线程能够正常运行,因为M需要和P绑定才能运行G,而P的个数取决于设置的GOMAXPROCS,一个M阻塞了就会创建新的M。
M的数量和P不一定匹配,可以设置很多M,M和P绑定后才可运行,多余的M处于休眠状态。
m结构体部分源码如下:
代码语言:javascript复制// 底层数据结构
type m struct {
g0 *g // goroutine with scheduling stack
divmod uint32
...
curg *g // M当前绑定的结构体G
...
}
这里的g0和curg是两个不同的goroutine。g0 是一个运行时中比较特殊的 Goroutine,它会深度参与运行时的调度过程,包括 Goroutine 的创建、大内存分配和 CGO 函数的执行,curg 是在当前线程上运行的用户 Goroutine。
关于g0的特殊性总结如下:
- g0 所有调用栈的goroutine,这是一个比较特殊的goroutine。
- 普通的goroutine栈是在Heap分配的可增长的stack,而g0的stack是M对应的线程栈。
- 所有调度相关代码,会先切换到该g0 goroutine的栈再执行。
P(Processor)
P理解为处理器,它是M和G的中间层,负责调度M上的等待队列,通过处理器 P 的调度,每一个M都能够执行多个 Goroutine。
P的个数由runtime.GOMAXPROCS进行指定,默认是被设置为可用的CPU核数,比如你有8核处理器,那么P的数量就是默认8个。
P需要和M进行绑定才能执行G,当 M 被阻塞时,整个 P 会被传递给其他 M ,或者说整个 P 被接管。
代码语言:javascript复制type p struct {
id int32
status uint32 // one of pidle/prunning/...
link puintptr
schedtick uint32 // incremented on every scheduler call
syscalltick uint32 // incremented on every system call
sysmontick sysmontick // last tick observed by sysmon
m muintptr // back-link to associated m (nil if idle)
...
}
总结
简单介绍了 Go 语言调度器中GMP的底层数据结构,包括线程 M、处理器 P 和 Goroutine G。三者关系:G需要绑定在M上才能运行,M需要绑定P才能运行,M 会从与它绑定的 P 的本地队列获取可运行的 G,还会从其他 P 偷 G。