Go 语言的 GPM 模型是指 G(Goroutine)、P(Processor)、M(Machine)的模型。这是 Go 语言在实现并发编程时所采用的一种轻量级线程管理调度系统。
简单来说:
1)G(Goroutine):表示 Go 中的协程,是由 Go 运行时管理的轻量级线程。Goroutine 的创建和销毁成本较低,且每个 Goroutine 分配的初始内存非常小,通常在几 KB 的量级,可以轻松创建成千上万个 Goroutine 而不会对系统资源产生巨大的压力。
2)P(Processor):代表逻辑处理器,负责管理可运行的 Goroutine 队列,并提供执行环境(如内存分配状态)给 M。P 的数量由 GOMAXPROCS 参数决定,限制了程序中同时并发的 Goroutine 数量。每个 P 维护一个本地队列,存储待执行的 Goroutine。
3)M(Machine):表示操作系统线程,负责执行分配给它的 Goroutine。每个 M 必须与一个 P 绑定才能执行 Goroutine。M 的数量不固定,由 Go 运行时根据需要动态调整。当一个 M 阻塞时(例如进行系统调用),运行时会将其绑定的 P 转移给其他空闲的 M。
GPM 模型的工作流程
- Goroutine 创建:当创建一个新的 Goroutine 时,运行时会将其封装为一个 G 对象,并尝试将其添加到当前 P 的本地队列中。如果本地队列已满,则会将部分 G (当前 G 和本地队列中一半的G)转移到全局队列中。
- 任务调度:M 从其绑定的 P 的本地队列中获取可运行的 G 并执行。如果本地队列为空,M 会尝试从全局队列获取,如果全局也为空,则会尝试从其他 P 的本地队列中窃取任务(称为 work stealing)。
- 阻塞处理:如果 M 在执行过程中发生阻塞(例如系统调用),运行时会将该 M 与 P 分离,并将 P 分配给其他空闲的 M,以继续执行其他 Goroutine。当阻塞的 M 恢复时,它会尝试获取一个空闲的 P 将阻塞恢复的 G 放入其中继续执行,如果没有空闲的 P,那么从阻塞恢复的 G 会被放回全局队列,然后 M 进入休眠状态。
抢占式调度