理解GPM协程调度模型工作原理之前,先了解下协程、线程、进程之间的区别:
进程是操作系统进行资源分配的基本单位,线程是CPU调度的基本单位,其中协程相对于CPU调度的线程可以区分为用户态的线程和内核态的线程,实际运行时,CPU仍然只切换内核态的线程,协程可以通过协程调度器,让多个用户态的线程队列和一个CPU轮转期内核态的线程绑定运行也即在用户态实现并发,以达到减少线程切换时CPU资源浪费的目的。
协程的目的是在执行多线程任务时,减少CPU在切换线程上的调度消耗,以达到提高CPU的利用率。
GPM的G是goroutine协程,P是processor处理器是抽象的处理器,包含了协程运行的环境资源,M是Machine线程,内核状态的线程。goroutine相比于广义的协程,占用更小的内存空间,只占几KB的内存(线程占用约4MB,广义协程也是MB级别),调度更加灵活
G 表示 Goroutine,也就是 Go 语言中的轻量级线程。Goroutine 是一种并发执行的抽象,它比传统的线程更轻量、更高效,并且可以很容易地创建和销毁。
P 表示 Processor,也可以理解为上下文处理器。每个 P 其实就是一个相当于操作系统线程的抽象,负责执行 Goroutine。P 的个数可以通过 GOMAXPROCS
环境变量或者 runtime.GOMAXPROCS
函数进行设置。
M 表示 Machine,也就是操作系统线程和内核的直接映射,它负责管理真实的线程资源,并与操作系统进行交互。在 GPM 调度模型中,有多个 M,可以与多个 P 绑定。
N 表示工作队列的长度。工作队列用来存放待执行的 Goroutine,当某个 P 处于空闲状态时,会从工作队列中取出 Goroutine 执行。
GPM 调度模型的工作流程如下:
- 当创建一个 Goroutine 时,它被放入当前 P 的本地队列(local run queue)中。
- 当本地队列为空时,P 会尝试从全局队列(global run queue)中偷取 Goroutine。
- 如果全局队列也为空,P 则会去其他空闲的 P 偷取 Goroutine。
- 当 P 获取到 Goroutine 后,将其放入自己的本地队列并执行。
- P 执行完毕后,会检查是否需要进行垃圾回收操作。
- 若某个 P 长时间没有进展(例如发生了系统调用或者进入了休眠状态),它会释放与之关联的 M,并允许其他 Goroutine 使用。
- 被释放的 M 可以绑定到其他的 P,并开始执行新的 Goroutine。
总结起来,GPM 调度模型通过将 Goroutine 分配给不同的 P 来实现并发执行,同时利用工作队列和偷取策略来平衡负载。这种调度模型在高并发、异步执行的场景下表现出色,使得开发者可以轻松地编写高效、高并发的程序。