深入解析 Go 语言的 GMP 模型底层实现(一)
引言
Go 语言以其简洁的并发模型和高效的调度器而闻名。GMP 模型是 Go 运行时系统的核心,包括 Goroutines (G)、Machines (M) 和 Processors (P)。在本文中,我们将从概念出发,逐步深入到 Go 运行时的源码,探讨 GMP 模型的底层实现原理。
Goroutines (G)
Goroutines 是 Go 语言中实现并发的基本单元。它们是用户态的轻量级线程,由 Go 运行时管理,比传统的操作系统线程更为高效。
源码分析
Goroutine 的结构体定义在 src/runtime/runtime2.go 文件中:
type g struct {
// 指向此 goroutine 的栈
stack stack
// 此 goroutine 正在执行的函数
// ...
}
每个 g 结构体都包含了一个 stack,用于存储局部变量和执行上下文。
Machines (M)
Machines 是 Go 运行时中与操作系统线程直接对应的实体。它们负责执行 Goroutines。
源码分析
Machine 的定义在 src/runtime/runtime2.go 中:
type m struct {
// 当前 Machine 正在执行的 Goroutine
g0 *g
// ...
}
m 结构体中的 g0 是一个特殊的 Goroutine,用于执行系统调用和垃圾回收等操作。
Processors (P)
Processors 是逻辑处理器,负责 Goroutines 的调度。每个 P 都有一个运行队列,用于存放待运行的 Goroutines。
源码分析
Processor 的定义也在 src/runtime/runtime2.go:
type p struct {
// 运行队列,存放待运行的 Goroutine
runq runq
// ...
}
runq 是一个固定大小的队列,用于存放等待运行的 Goroutines。
调度器的工作原理
Go 调度器的主要任务是将 Goroutines 分配给可用的 Machine 执行。以下是调度器工作的基本步骤:
创建 Goroutine:当通过
go关键字启动一个新的 Goroutine 时,Go 运行时会为其创建一个新的g结构体。放入运行队列:新创建的 Goroutine 被放入当前 P 的运行队列。
调度:当一个 M 需要执行 Goroutine 时,它会从自己的 P 的运行队列中取出一个 Goroutine 来执行。
上下文切换:当 M 从一个 Goroutine 切换到另一个 Goroutine 时,会进行上下文切换,保存当前 Goroutine 的执行状态,并恢复下一个 Goroutine 的状态。
源码深度解析
创建 Goroutine
创建 Goroutine 的代码位于 src/runtime/proc.go:
func newproc(siz int) *g {
// ...
gp := getg() // 获取当前 M 的 g0
// ...
systemstack(func() {
// 在系统栈上执行,避免影响用户栈
p :=执 p() // 获取一个可用的 P
mp := acquirem() // 获取一个 M
mp.nextp.set(p) // 将 M 与 P 关联
mp.g0.set(gp) // 设置 M 的 g0 为当前的 g0
casgstatus(gp, _Gwaiting, _Grunning) // 改变 gp 的状态为 _Grunning
// ...
})
// ...
return g
}
调度循环
调度器的调度循环在 src/runtime/proc.go 中:
func schedule() {
// ...
gp := getg()
// ...
for {
gp.stackguard0 = stackPreempt{stack: gp.stack.hi} // 设置栈的大小
var gp1 guintptr
// ...
if gp1 == nil {
// 从全局运行队列中获取 Goroutine
gp1 = globrunqget(gp, 1)
}
if gp1 != nil {
// ...
casgstatus(gp1, _Gwaiting, _Grunning) // 改变状态为 _Grunning
// ...
}
gp1.m.set(mp) // 设置 Goroutine 的 M
// ...
execute(gp1, gp) // 执行 Goroutine
dropg() // 清理当前的 g
// ...
}
}
结语
Go 语言的 GMP 模型是其并发机制的基石,通过高效的调度器实现了 Goroutine 的轻量级调度。本文从概念出发,结合源码分析了 Go 运行时系统中 Goroutines、Machines 和 Processors 的底层实现。理解这些原理对于编写高效的并发程序至关重要。随着 Go 语言的不断发展,其调度器也在不断优化,未来可能会有更多令人激动的特性加入,进一步推动并发编程的边界。
