G,M,P分别是什么
-
G,Goroutine,就是协程。存储于P的本地队列或者是全局队列中。
-
M,Machine,就是Work Thread,就是传统意义的线程,用于执行Goroutine,G。只有在M与具体的P绑定后,才能执行P中的G。
-
P,Processor,处理器,主要用于协调G和M之间的关系,存储需要执行的G队列,与特定的M绑定后,执行Go程序,也就是G。
上图:
M就是线程,由操作系统负责调度,cpu中执行
G可以存在全局队列,也可存在本地队列(下面会详细讲述,每个本地队列也就是P可以存256个G)
P与M建立联系,在P中执行G(P并不与一个固定的M简历联系如果M阻塞,P会与新的M联系)
GMP的数量问题
P由cpu的核数决定,一般情况P的数量就是cpu的核数,P的数量可以通过函数:runtime.GOMAXPROCS设置
G的数量时没有限定的
M的数量有上限,10000个,但是一般达不到,有几个因素可以影响M的数量:CPU的核数,GOMAXPROCS的数量,操作系统的线程限制
P与G的关联
新创建的G会首先存在P(本地队列里面),如果P满了,会存到全局队列中,同时把P中的一半的G也放在全局队列里面,之后有新的G还会先存到P里面
那么当P执行G的时候:
首先从P里面查找,本地没有G的时候,从其他的P里面拿G,一般1也是一半, 当所有P都没有的时候 , 从全局队列拿G,一次会获取多个
还有一种情况就是P里面永远有G怎么办:
当全局运行队列中有待执行的 G 时,还会有固定几率(每61个调度时钟周期 schedtick)会从全局的运行队列中查找对应的 G,为了保证全局G队列一定可以被调度。
P与M关联
只有M才能真正的执行G,所以当要执行G的时候P需要与M关联,(每个M只能同时执行一个G)
当P需要执行,但是没有空闲M的时候,会创建新的M来与P建立联系
当M上的G运行耗时活着阻塞的时候,M与释放P转给其他M执行,当M执行完的系统调用阻塞的G后,M会尝试获取新的空闲P,同时将G放入P的本地队列执行。若没有空闲的P,则将G放入全局G队列,M进入休眠,等待被唤醒或被垃圾回收
M0和G0
-
M0, 启动程序后的编号为 0 的主线程,负责执行初始化操作和启动第一个 G,也就是 main Goroutine。之后与其他M一样调度。
-
G0,每个 M 创建的第一个 Goroutine。G0 仅用于负责调度的 G,G0 不指向任何可执行的函数,每个 M 都会有一个自己的 G0。在调度或系统调用时会使用 G0 的栈空间。
协作和抢占调度
当某个 G 执行时间过长,其他的 G 如何调度。通常有两种方案:
-
协作式,主动让出执行权,让其他G执行。通过runtime.Gosched()可以让出执行权。
-
抢占式,被动让出执行权,也就是调度器将G的执行权取消,分配给其他的G。Go目前默认的方式。在Go中一个G最多可以执行10ms,超时就会被让出调度权。