8

golang的GMP调度

 3 years ago
source link: https://studygolang.com/articles/33233
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Golang 调度器四个重要结构 :M P G Sched

GMP的结构源码在文件中\src\runtime\runtime2.go

简介

  • G :goroutine,go程序建立的用户线程。主要保存 goroutine 的运行时栈信息(stack结构体)以及 CPU 的一些寄存器的值(gobuf结构体),还有关联的M,全局队列中下个G等信息。
  • M :machine 一个 M 直接关联一个os内核线程,用于执行G。 M 会优先从关联的 P 的本地队列中直接获取待执行的 G ,它保存了 M 自身使用的栈信息、当 前正在 M 上执行的 G 信息、与之绑定的 P 信息。
  • P :processor 代表了 M 所需的上下文环境,也是处理用户级代码逻辑的处理器,可以看作一个局部调度器使go代码在一个线程上跑。
  • P列表 :在创建程序的时候创建一个 P 列表, 最多有$GOMAXPROCS个,这环境变量可以通过操作系统中的环境变量设置,也可以通过Go程序中的runtime.GOMAXPROCS()函数设置,默认为处理器的核心数,它代表了真正的并发度。
  • M列表 :当前操作系统分配到当前go程序的内核线程数,可以通过go语言中runtime/debug包中的SetMaxThreads函数设置。当有一个 M 阻塞,会有一个新的M被创建;当有一个 M 空闲,会被回收或睡眠。
  • P的本地队列 :P维护一个runq_用来存放等待执行的goroutine,新创建的 G 会优先放在 P 的本地队列,当本地队列满(256G)时,会放入 G 的全局队列。
  • 全局队列 :如果 P 的本地队列已满,待执行的 G 就会放在全局队列中, M 会先从关联的 P本地队列中获取 待执行的 G ,没有的话,再到 全局队列 中获取;如果这里也没有了,就去 其他 P 的本地队列 中获取一些任务。

GMP调度

qE36Zfj.png!mobile

image.png

上图讲的是两个线程(内核线程)的情况。一个M会对应一个内核线程,一个M也会连接一个上下文(context)P,一个上下文连接一个或者多个Goroutine。图中P正在执行的 Goroutine 为蓝色的;处于待执行状态的 Goroutine 为灰色的,灰色的 Goroutine 形成了一个队列 runqueues 。P(Processor)的数量是在启动时被设置为环境变量GOMAXPROCS的值,或者通过运行时调用函数 runtime.GOMAXPROCS() 进行设置。Processor数量固定意味着任意时刻只有固定数量的线程在运行go代码。Goroutine中就是我们要执行并发的代码。

为何要维护多个上下文P?因为当一个OS线程被阻塞时,P可以转而投奔另一个OS线程!

下图中看到,当一个OS线程M0陷入阻塞时,P转而在OS线程M1上运行。调度器保证有足够的线程来运行所以的context P。一个很简单的例子就是系统调用 syscall ,一个线程肯定不能同时执行代码和系统调用被阻塞,这个时候,此线程M需要放弃当前的上下文环境P,以便可以让其他的 Goroutine 被调度执行。

YFf2eqe.png!mobile

image.png

如上图左图所示,M0中的G0执行了syscall,然后就创建了一个M1(也有可能本身就存在,没创建),(转向右图)然后M0丢弃了P,等待syscall的返回值,M1接受了P,将·继续执行 Goroutine 队列中的其他 Goroutine

当系统调用syscall结束后,M0会“偷”一个上下文,如果不成功,M0就把它的Gouroutine G0放到一个全局的runqueue中,然后自己放到线程池或者转入休眠状态。全局runqueue是各个P在运行完自己的本地的Goroutine runqueue后用来拉取新goroutine的地方。P也会周期性的检查这个全局runqueue上的goroutine,否则,全局runqueue上的goroutines可能得不到执行而饿死。

RnyiAzV.png!mobile

image.png

另一种情况是P所分配的任务G很快就执行完了(分配不均),这就导致了一个上下文P闲着没事儿干而系统却任然忙碌。但是如果global runqueue没有任务G了,那么P就不得不从其他的上下文P那里拿一些G来执行。一般来说,如果上下文P从其他的上下文P那里要偷一个任务的话,一般就‘偷’run queue的一半,这就确保了每个OS线程都能充分的使用。

有疑问加站长微信联系(非本文作者)

eUjI7rn.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK