124 lines
4.2 KiB
Markdown
124 lines
4.2 KiB
Markdown
---
|
||
tags: [go, golang, go-principle, gmp-scheduler, datastructure]
|
||
create time: 2026-06-07 16:00
|
||
---
|
||
|
||
# GMP 调度原理 — 数据结构
|
||
|
||
## 概述
|
||
|
||
本节深入 `runtime/runtime2.go`,逐字段拆解 G、M、P、schedt 四个核心结构体的源码定义及其设计意图。理解这些结构是后续追踪调度流程的基础。
|
||
|
||
## 正文
|
||
|
||
### 2.1 G(Goroutine)
|
||
|
||
G 是 goroutine 的运行时实体,承载栈空间、执行状态和生命周期信息。
|
||
|
||
```go
|
||
type g struct {
|
||
stack stack // 栈空间 [stack.lo, stack.hi)
|
||
stackguard0 uintptr // 栈保护区边界;也用于传递抢占标记
|
||
|
||
_panic *_panic // panic 链表头
|
||
_defer *_defer // defer 链表头(LIFO)
|
||
m *m // 当前绑定的 M(未运行时为 nil)
|
||
atomicstatus uint32 // 原子状态:_Gidle → _Grunnable → _Grunning → ...
|
||
|
||
schedlink guintptr // 全局队列 / 空闲链表中的 next 指针
|
||
}
|
||
```
|
||
|
||
| 关键字段 | 说明 |
|
||
|----------|------|
|
||
| `stack` | Goroutine 的执行栈,初始约 2KB,可动态扩容 |
|
||
| `stackguard0` | 函数调用前比较值。若等于 `stackPreempt` 表示被标记抢占;若接近 `stack.lo` 则触发栈扩容 |
|
||
| `atomicstatus` | 生命周期状态的原子快照,通过 `casgstatus()` 切换 |
|
||
|
||
**状态流转**:
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> _Gidle: 未初始化
|
||
_Gidle --> _Grunnable: newproc()
|
||
_Grunnable --> _Grunning: schedule() 从队列取出
|
||
_Grunning --> _Grunnable: Gosched() / preempt()
|
||
_Grunning --> _Gdead: 执行完毕
|
||
_Grunning --> _Gwaiting: gopark() 阻塞
|
||
_Gwaiting --> _Grunnable: goready() 唤醒
|
||
_Gwaiting --> _Gsyscall: (间接)
|
||
_Gsyscall --> _Grunning: exitsyscall()
|
||
_Gdead --> [*]: 回收
|
||
style _Grunning fill:#e8f5e9
|
||
style _Gwaiting fill:#fff3e0
|
||
```
|
||
|
||
### 2.2 M(Machine)
|
||
|
||
M 是 OS 线程的运行时封装,真正执行代码。
|
||
|
||
```go
|
||
type m struct {
|
||
g0 *g // 调度协程,每个 M 独有
|
||
procid uint64 // M 的唯一 ID
|
||
gsignal *g // 信号处理协程
|
||
curg *g // 当前正在运行的用户 G
|
||
p puintptr // 当前绑定的 P
|
||
schedlink muintptr // 空闲 M 链表 next 指针
|
||
}
|
||
```
|
||
|
||
M 在两个角色间切换:
|
||
- 执行 `g0` 时:**调度者**——调用 `schedule()` 寻找下一个待执行的 G
|
||
- 执行 `curg` 时:**执行者**——运行用户代码
|
||
|
||
### 2.3 P(Processor)
|
||
|
||
P 是逻辑处理器,作为调度器的核心组件管理本地队列。
|
||
|
||
```go
|
||
type p struct {
|
||
id int32 // P 的编号
|
||
status uint32 // _Pidle / _Prunning / _Psyscall
|
||
link puintptr // 空闲 P 链表
|
||
schedtick uint32 // 每次 schedule() 自增
|
||
syscalltick uint32 // 每次系统调用自增
|
||
m muintptr // 回指绑定的 M(idle 时为 0)
|
||
|
||
runqhead uint32 // LRQ 头部索引
|
||
runqtail uint32 // LRQ 尾部索引
|
||
runq [256]guintptr // 本地 G 队列(环形数组)
|
||
runnext guintptr // VIP 位置:高优先级下一个 G
|
||
}
|
||
```
|
||
|
||
| 关键字段 | 作用 |
|
||
|----------|------|
|
||
| `runq[256]` | 定长环形数组作为 LRQ,CAS 无锁存取 |
|
||
| `runnext` | 新创建的 G 优先放入此处,下次调度直接执行,跳过队列开销 |
|
||
| `schedtick` | 配合防饥饿机制:`schedtick % 61 == 0` 时检查 GRQ |
|
||
|
||
### 2.4 schedt(全局调度器)
|
||
|
||
`schedt` 管理跨 P 的全局资源,访问需持有 `sched.lock`。
|
||
|
||
```go
|
||
type schedt struct {
|
||
lock mutex // 全局互斥锁
|
||
midle muintptr // 空闲 M 队列
|
||
pidle puintptr // 空闲 P 队列
|
||
runq gQueue // 全局 G 队列(GRQ)
|
||
runqsize int32 // GRQ 中 G 的数量
|
||
// ...
|
||
}
|
||
```
|
||
|
||
> [!note] 📝 idle 队列的设计意图
|
||
> `midle` 和 `pidle` 实现了资源的休眠与复用——不忙时释放回池中,有新任务时快速唤醒,避免 CPU 空转或频繁创建线程。
|
||
|
||
## 关联笔记
|
||
|
||
- [[hzh/GolangStar/Go语言原理/gmp调度原理/gmp-overview]] — GMP 概览
|
||
- [[hzh/GolangStar/Go语言原理/gmp调度原理/gmp-lifecycle]] — 创建与调度流程
|
||
- [[hzh/GolangStar/Go语言原理/gmp调度原理/gmp-preemption]] — 抢占机制
|