关于 G、P、M 的定义,我们可以参见$GOROOT/src/runtime/runtime2.go
这个源文件。G、P、M 这三个结构体定义都是大块儿头,每个结构体定义都包含十几个甚至二、三十个字段。像调度器这样的核心代码向来很复杂,考虑的因素也非常多,代码“耦合”成一坨。不过从复杂的代码中,我们依然可以看出来 G、P、M 的各自大致用途,这里简要说明一下:
- G: 代表 goroutine,存储了 goroutine 的执行栈信息、goroutine 状态以及 goroutine 的任务函数等;另外 G 对象是可以重用的。
- P: 代表逻辑 processor,P 的数量决定了系统内最大可并行的 G 的数量(前提:系统的物理 CPU 核数>=P 的数量);P 的最大作用还是其拥有的各种 G 对象队列、链表、一些缓存和状态。
- M: M 代表着真正的执行计算资源。在绑定有效的 P 后,进入一个调度循环;而调度循环的机制大致是从各种队列、P 的本地运行队列中获取 G,切换到 G 的执行栈上并执行 G 的函数,调用 goexit 做清理工作并回到 M。如此反复。M 并不保留 G 状态,这是 G 可以跨 M 调度的基础。