GMP 模型
gmp模型是 golang 中用于调度管理 goroutine 的调度器。
调度器的发展史
在 Go 语言中,Goroutine 早期是没有设计成抢占式的,早期 Goroutine 只有读写、主动让出、锁等操作时才会触发调度切换。
这样有一个严重的问题,就是垃圾回收器进行 STW 时,如果有一个 Goroutine 一直都在阻塞调用,垃圾回收器就会一直等待他,不知道等到什么时候…
这种情况下就需要抢占式调度来解决问题。如果一个 Goroutine 运行时间过久,就需要进行抢占来解决。
这块 Go 语言在 Go1.2 起开始实现抢占式调度器,不断完善直至今日:
- Go0.x:基于单线程的程调度器。
- Go1.0:基于多线程的调度器。
- Go1.1:基于任务窃取的调度器。
- Go1.2 - Go1.13:基于协作的抢占式调度器。
- Go1.14:基于信号的抢占式调度器。
为什么要抢占 P
为什么会要想去抢占 P 呢,说白了就是不抢,就没机会运行,会饿死。又或是资源分配不均了,这在调度器设计中显然是不合理的。
func main() {// 模拟单核 CPUruntime.GOMAXPROCS(1)// 模拟 Goroutine 死循环go func() {for {}}()time.Sleep(time.Millisecond)fmt.Println("1111111")
}
这个例子在老版本(1.14版本之前)的 Go 语言中,就会一直阻塞,没法重见天日,是一个需要做抢占的场景。
但可能会有小伙伴问,抢占了,会不会有新问题。因为原本正在使用 P 的 M 就凉凉了(M 会与 P 进行绑定),没了 P 也就没法继续执行了。
这其实没有问题,因为该 Goroutine 已经阻塞在了系统调用上,暂时是不会有后续的执行新诉求。
但万一代码是在运行了好一段时间后又能够运行了(业务上也允许长等待),也就是该 Goroutine 从阻塞状态中恢复了,期望继续运行,没了 P 怎么办?
这时候该 Goroutine 可以和其他 Goroutine 一样,先检查自身所在的 M 是否仍然绑定着 P:
- 若是有 P,则可以调整状态,继续运行。
- 若是没有 P,可以重新抢 P,再占有并绑定 P,为自己所用。
也就是抢占 P,本身就是一个双向行为,你抢了我的 P,我也可以去抢别人的 P 来继续运行。