GMP 为什么要有 P ?

一个协程得以运行,需要同时满足以下两个条件:

  1. P 已经和某个线程进行绑定,这样才能参考操作系统的调度获得 CPU 时间
  2. P 已经从队列中(可以是本地队列,也可以是全局队列,甚至是从其他 P 的队列)取到该协程

第一个条件就是 操作系统调度,而第二个其实就是 Go 里的调度器

操作系统调度

假设一台机器上有两个 CPU 核心,意味着,同时在同一时间里,只能有两个线程运行着。

可如果该机器上实际开启了 4 个线程,要是先执行一个线程完再执行另一个线程,那么当某一个线程因为一些阻塞性的系统调用而阻塞时,CPU 的时间就会因此而白白浪费掉了。

更合适的做法是,使用 操作系统调度策略,设定一个调度周期,假设是 10ms (毫秒),那在一个周期里,每个线程都平均分,都只能得到 2.5ms 的CPU 运行时间。

可如果机器上有 1000 个线程呢?难道每个线程都分个 0.01 ms (也就是 10 微秒)吗?

要知道,CPU 从 A 线程切换到 B 线程,是有巨大的时间浪费在线程上下文的切换,如果切换得太频繁,就会有大量的 CPU 时间白白浪费。

Go 语言面试题 100 讲之 017篇:说一下 GMP 模型的原理

因此,通常会限制最小的时间片的长度,假设为 2ms,受此调整,现在调度周期就会变成 2*1000 = 2s 。

 

GM 模型是怎样的?

在 Go v1.1 之前,实际上 GMP确实是没有 P 的,所有的 M 线程都要从 全局队列中获取 G 来执行任务,为了避免冲突,从全局队列中获取 G 的时候,要先获取一把大锁。

当一个程序的并发量比较小的时候,影响还不大,而当程序的并发量非常大的时候,这个全局队列会成为性能的瓶颈。

除此之外 ,若直接把 G 从全局队列分配给 M,那么当 G 中当生系统调用或者其他阻塞性的操作时,M 会有一段时间处于挂起的状态,此时又没有新创建线程的线程来代替该线程继续从队列中取出其他 G 来运行,从效率上其实会打折扣。

P 带来的改变

加了 P 之后会带来什么改变呢?

  • 每个 P 有自己的本地队列,大幅度的减轻了对全局队列的直接依赖,所带来的效果就是锁竞争的减少。而 GM 模型的性能开销大头就是锁竞争。
  • 当一个 M 中 运行的 G 发生阻塞性操作时,P 会重新选择一个 M,若没有 M 就新创建一个 M 来继续从 P 本地队列中取 G 来执行,提高运行效率。
  • 每个 P 相对的平衡上,在 GMP 模型中也实现了 Work Stealing 算法,如果 P 的本地队列为空,则会从全局队列或其他 P 的本地队列中窃取可运行的 G 来运行,减少空转,提高了资源利用率。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章