Files
cs-note/hzh/GolangStar/Go语言原理/gmp调度原理/gmp-datastructures.md
T

124 lines
4.2 KiB
Markdown
Raw Normal View History

2026-06-07 16:52:15 +08:00
---
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]] — 抢占机制