golang中G、P、M 和 sched 三者的数据结构

G、P、M 三者是golang实现高并发能的最为重要的概念,runtime 通过 调度器 来实现三者的相互调度执行,通过 p 将用户态的 g 与内核态资源 m 的动态绑定来执行,以减少以前通过频繁创建内核态线程而产生的一系列的性能问题,充分发挥服务器最大有限资源。

GPM 协作

调度器的工作是将一个 G(需要执行的代码)、一个 M(代码执行的地方)和一个 P(代码执行所需要的权限和资源)结合起来。

所有的 g、m 和 p 对象都是分配在上且永不释放的,所以它们的内存使用是很稳定的。得益于此,runtime 可以在调度器实现中避免写屏障。当一个G执行完成后,可以放入pool中被再次使用,避免重复申请资源。

本节主要通过阅读runtime源码来认识这三个组件到底长的是什么样子,以此加深对 GPM 的理解。go version go1.15.6

理解下文前建议先阅读一下 src/runtime/HACKING.md 文件,中文可阅读这里,这个文件是面向开发者理解runtime的。

本文若没有指定源码文件路径,则默认为 src/runtime/runtime2.go

G

G是英文字母goroutine的缩写,一般称为“协程”,其实这个词还是无法完整表达它的意思的,但这用的人的多了就成了统称。注意它与线程和进程的区别,这个应该很容易理解,每个gopher应该都知道。

每个 Goroutine 对应一个 g 结构体,它有自己的栈内存, G 存储 Goroutine 的运行堆栈、状态以及任务函数,可重复用,

Goroutine数据结构位于 src/runtime/runtime2.go 文件,注意此文件里有太多重要的底层数据结构,对于我们理解底层runtime非常的重要,建议大量多看看。不需要记住每一个数据结构,但需要的时候要能第一时间想到在哪里查找。

当一个 goroutine 退出时,g 对象会被放到一个空闲的 g 对象池中以用于后续的 goroutine 的使用, 以减少内存分配开销。

Continue reading