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

124 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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]] — 抢占机制