Go中的调度器:GPM模型详解与调度流程
字数 1690 2025-12-01 08:55:41

Go中的调度器:GPM模型详解与调度流程

描述
Go语言的并发核心是Goroutine,而Goroutine的高效调度依赖于Go运行时独特的GPM模型。GPM分别代表Goroutine(G)、逻辑处理器(P)和操作系统线程(M)。这个模型通过工作窃取(Work Stealing)和线程复用等机制,实现了高效的并发调度。理解GPM模型对于编写高性能并发程序和诊断调度相关问题至关重要。

知识点详解

1. GPM模型的基本组成

G(Goroutine)

  • 轻量级用户态线程,初始栈大小约2KB,可动态扩容
  • 包含执行栈、状态、程序计数器等基本信息
  • 状态包括:_Gidle(刚分配)、_Grunnable(可运行)、_Grunning(运行中)、_Gsyscall(系统调用中)、_Gwaiting(等待中)、_Gdead(已终止)

P(Processor)

  • 逻辑处理器,GOMAXPROCS决定P的数量
  • 维护本地Goroutine队列(local runqueue,LRQ),最大256个G
  • 持有需要与M绑定的内存资源(如mcache)
  • 状态:_Pidle(空闲)、_Prunning(运行中)、_Psyscall(系统调用中)、_Pgcstop(GC停止中)

M(Machine)

  • 操作系统线程的抽象,真正执行计算的资源
  • 每个M必须绑定一个P才能执行G
  • 同时只能运行一个G,但可以在多个G之间切换
  • 持有执行G的栈内存等资源

2. GPM的关联关系

P --- 绑定 --- M
|             |
|           执行
LRQ           G
(本地队列)
  • 一个P可以绑定到多个M(非同时),一个M必须绑定一个P
  • 每个P维护一个本地G队列,全局还有一个全局G队列(GRQ)
  • 当P的本地队列为空时,会从其他P"窃取"G或从全局队列获取G

3. 调度流程详解

步骤1:Goroutine创建

go func() {
    // 新goroutine
}()
  • 新G被创建,优先放入当前P的本地队列
  • 如果本地队列已满(256个),将一半G转移到全局队列

步骤2:Goroutine执行

  1. M从绑定的P的本地队列获取G
  2. 如果本地队列为空,按顺序尝试:
    • 从全局队列获取一批G(最多n = min(len(GRQ)/GOMAXPROCS + 1, len(GRQ)/2))
    • 从其他P的本地队列窃取一半的G
  3. M执行G,直到发生以下情况:

步骤3:调度时机

  • 主动让出:goyield()、Gosched()主动让出CPU
  • 系统调用:G进入系统调用,P可能与M解绑
  • 通道操作:G在channel上阻塞
  • 网络I/O:G在network poller上等待
  • 垃圾回收:GC需要停止所有G

4. 系统调用处理

场景:G执行阻塞系统调用

  1. G进入_Gsyscall状态
  2. P与当前M解绑,进入_Psyscall状态
  3. 调度器将P绑定到新的M(或创建新M)继续执行其他G
  4. 系统调用完成后,G尝试:
    • 找回原来的P,如果P可用则继续执行
    • 如果P已被占用,将G放入全局队列,M进入休眠

5. 工作窃取机制

当P的本地队列为空时,执行work-stealing算法:

  1. 随机选择一个其他P作为窃取目标
  2. 从队列尾部窃取一半的G(减少竞争)
  3. 如果所有其他P的队列都为空,检查全局队列
  4. 如果全局队列也为空,从网络轮询器获取就绪的G

6. 网络I/O调度

Go使用netpoller处理网络I/O:

  • G在network I/O上阻塞时,不会阻塞M
  • G被移动到netpoller,M可以执行其他G
  • 当I/O就绪时,netpoller将G放回某个P的队列

示例演示调度过程

func main() {
    runtime.GOMAXPROCS(2) // 设置2个P
    
    for i := 0; i < 5; i++ {
        go worker(i)
    }
    
    time.Sleep(time.Second)
}

func worker(id int) {
    fmt.Printf("Worker %d on P%d\n", id, getP())
    time.Sleep(100 * time.Millisecond)
}

// 获取当前P的ID(简化演示)
func getP() int {
    // 实际需要通过runtime包或调试信息获取
    return 0 // 简化返回
}

调度过程分析:

  1. 主G创建5个worker G
  2. 2个P各自从本地队列获取G执行
  3. 当G执行sleep时,P调度其他可运行的G
  4. 通过工作窃取平衡各P的负载

总结
GPM模型通过逻辑处理器(P)解耦了Goroutine(G)和系统线程(M),实现了:

  • 高效调度:用户态调度,切换成本低
  • 负载均衡:工作窃取避免某些P空闲
  • 线程复用:减少线程创建销毁开销
  • 非阻塞I/O:netpoller集成提高I/O效率

理解GPM模型有助于诊断goroutine泄漏、调度延迟等并发问题,是掌握Go并发编程的关键基础。

Go中的调度器:GPM模型详解与调度流程 描述 Go语言的并发核心是Goroutine,而Goroutine的高效调度依赖于Go运行时独特的GPM模型。GPM分别代表Goroutine(G)、逻辑处理器(P)和操作系统线程(M)。这个模型通过工作窃取(Work Stealing)和线程复用等机制,实现了高效的并发调度。理解GPM模型对于编写高性能并发程序和诊断调度相关问题至关重要。 知识点详解 1. GPM模型的基本组成 G(Goroutine) 轻量级用户态线程,初始栈大小约2KB,可动态扩容 包含执行栈、状态、程序计数器等基本信息 状态包括:_ Gidle(刚分配)、_ Grunnable(可运行)、_ Grunning(运行中)、_ Gsyscall(系统调用中)、_ Gwaiting(等待中)、_ Gdead(已终止) P(Processor) 逻辑处理器,GOMAXPROCS决定P的数量 维护本地Goroutine队列(local runqueue,LRQ),最大256个G 持有需要与M绑定的内存资源(如mcache) 状态:_ Pidle(空闲)、_ Prunning(运行中)、_ Psyscall(系统调用中)、_ Pgcstop(GC停止中) M(Machine) 操作系统线程的抽象,真正执行计算的资源 每个M必须绑定一个P才能执行G 同时只能运行一个G,但可以在多个G之间切换 持有执行G的栈内存等资源 2. GPM的关联关系 一个P可以绑定到多个M(非同时),一个M必须绑定一个P 每个P维护一个本地G队列,全局还有一个全局G队列(GRQ) 当P的本地队列为空时,会从其他P"窃取"G或从全局队列获取G 3. 调度流程详解 步骤1:Goroutine创建 新G被创建,优先放入当前P的本地队列 如果本地队列已满(256个),将一半G转移到全局队列 步骤2:Goroutine执行 M从绑定的P的本地队列获取G 如果本地队列为空,按顺序尝试: 从全局队列获取一批G(最多n = min(len(GRQ)/GOMAXPROCS + 1, len(GRQ)/2)) 从其他P的本地队列窃取一半的G M执行G,直到发生以下情况: 步骤3:调度时机 主动让出 :goyield()、Gosched()主动让出CPU 系统调用 :G进入系统调用,P可能与M解绑 通道操作 :G在channel上阻塞 网络I/O :G在network poller上等待 垃圾回收 :GC需要停止所有G 4. 系统调用处理 场景:G执行阻塞系统调用 G进入_ Gsyscall状态 P与当前M解绑,进入_ Psyscall状态 调度器将P绑定到新的M(或创建新M)继续执行其他G 系统调用完成后,G尝试: 找回原来的P,如果P可用则继续执行 如果P已被占用,将G放入全局队列,M进入休眠 5. 工作窃取机制 当P的本地队列为空时,执行work-stealing算法: 随机选择 一个其他P作为窃取目标 从队列尾部 窃取一半的G(减少竞争) 如果所有其他P的队列都为空,检查全局队列 如果全局队列也为空,从网络轮询器获取就绪的G 6. 网络I/O调度 Go使用netpoller处理网络I/O: G在network I/O上阻塞时,不会阻塞M G被移动到netpoller,M可以执行其他G 当I/O就绪时,netpoller将G放回某个P的队列 示例演示调度过程 调度过程分析: 主G创建5个worker G 2个P各自从本地队列获取G执行 当G执行sleep时,P调度其他可运行的G 通过工作窃取平衡各P的负载 总结 GPM模型通过逻辑处理器(P)解耦了Goroutine(G)和系统线程(M),实现了: 高效调度 :用户态调度,切换成本低 负载均衡 :工作窃取避免某些P空闲 线程复用 :减少线程创建销毁开销 非阻塞I/O :netpoller集成提高I/O效率 理解GPM模型有助于诊断goroutine泄漏、调度延迟等并发问题,是掌握Go并发编程的关键基础。