vault backup: 2026-06-07 16:52:15
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
---
|
||||
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]] — 抢占机制
|
||||
Reference in New Issue
Block a user