深入解析 Go 语言的 GMP 模型底层实现(一)

深入解析 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 执行。以下是调度器工作的基本步骤:

  1. 创建 Goroutine:当通过 go 关键字启动一个新的 Goroutine 时,Go 运行时会为其创建一个新的 g 结构体。

  2. 放入运行队列:新创建的 Goroutine 被放入当前 P 的运行队列。

  3. 调度:当一个 M 需要执行 Goroutine 时,它会从自己的 P 的运行队列中取出一个 Goroutine 来执行。

  4. 上下文切换:当 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 语言的不断发展,其调度器也在不断优化,未来可能会有更多令人激动的特性加入,进一步推动并发编程的边界。