Golang调度器

目录

大量的进程/线程出现了新的问题

对于没有协程的程序,我们启动多少个线程比较合适

GMP模型(golang调度器模型)

调度器组成部分

M与P的关系

调度器的选取原则

调度流程

work steal

Golang的版本

参考文献


大量的进程/线程出现了新的问题

  • 高内存占用(每个线程需要占用4M的内存)
  • 调度的高消耗CPU

N个协程绑定1个线程,优点就是协程在用户态线程即完成切换,不会陷入到内核态,这种切换非常的轻量快速

对于没有协程的程序,我们启动多少个线程比较合适

要回答这个问题,我们首先需要确定使用服务器的cpu core的个数,因为我们使用的线程数至少要大于等于cpu core数目,才能保证有机会完全使用cpu的性能。

对于cpu密集型的程序,(工作)线程数目稍微大于cpu core数据就可以了,因为每个线程都会很忙,如果添加更多的线程,反而会引起os调度器的分时调度,调度产生的context switch会带来额外的开销,另外寄存器內的数据局部性也会丢失。

对于io密集型的程序,添加线程的个数是有必要的。当一个线程发生读写文件、网络,或者触发系统调用,均会造成程序由运行态转换成等待态,因此该线程会被os调度器给移除(直到等待的事件发生)。这时,如果有其它需要工作的线程存在,它就可以被os的调度器调度上cpu上运行。至于线程的个数和cpu core数目的关系,可以通过参数调试逐步摸索出最合适的值。

GMP模型(golang调度器模型)

P – Logic Processor 默认为逻辑处理器的个数,也可以通过环境变量$GOMAXPROCS或者runtime. $GOMAXPROCS

M – Machine 操作系统的线程

  1. 默认10000以内
  2. 通过runtime.SetMaxThreads控制最大线程数
  3. 一个goroutine阻塞后会创建一个新的线程(如果当前没有空闲的)

G – 协程

调度器组成部分

  1. 全局队列(Global Queue):存放等待运行的G。
  2. P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G'时,G'优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列。
  3. P列表:所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置)个。
  4. M:线程想运行任务就得获取P,从P的本地队列获取G,P队列为空时,M也会尝试从全局队列一批G放到P的本地队列,或从其他P的本地队列一半放到自己P的本地队列。M运行G,G执行之后,M会从P获取下一个G,不断重复下去。

M与P的关系

MP的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来。

调度器的选取原则

  1. only 1/61 of the time, check the global runnable queue for a G.
  2. if not found, check the local queue.
  3. if not found, try to steal from other Ps.
  4. if not, check the global runnable queue.
  5. if not found, poll network.

通常每个goroutine的运行时间为10ms(基于信号量的抢占),超过后调度器会选择下一个goroutine进行运行

调度流程

 

从上图我们可以分析出几个结论:

​ 1、我们通过 go func()来创建一个goroutine

​ 2、有两个存储G的队列,一个是局部调度器P的本地队列、一个是全局G队列。新创建的G会先保存在P的本地队列中(数据的临近性),如果P的本地队列已经满了就会保存在全局的队列中;

​ 3G只能运行在M中,一个M必须持有一个PMP11的关系。M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会向其他的MP组合偷取一个可执行的G来执行;

​ 4、一个M调度G执行的过程是一个循环机制;

​ 5、当M执行某一个G时候如果发生了syscall或则其余阻塞操作,M会阻塞,如果当前有一些G还需要执行,runtime会把这个线程MP中摘除(detach),然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P

​ 6、当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列中。

work steal

What is great about all this work stealing is that it allows the Ms to stay busy and not go idle. This work stealing is considered internally as spinning the M.(如何保证性能与功耗之间的平衡?)

Golang的版本

1.14之前

主要是在函数调用时会启动调度器,如果函数是个死循环的话,调度器无法把它切走

1.14之后

基于信号量(抢占式)的调度

参考文献

https://segmentfault.com/a/1190000021951119

Scheduling In Go : Part II - Go Scheduler

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值