jinzh notes
jinzh notes

Goroutine调度机制

Goroutine调度机制
内容纲要

前言

复习一下goroutine的调度机制,为实习面试做准备

线程池

传统的线程调度中,为了减高并发场景中少线程的创建和销毁的时间开销,采取一种比较独特的方式,线程池

维基百科:

线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。

线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

线程池的方案简述是这样的:


存在一个任务队列,里面放置要执行的任务,我们称里面的任务为G

线程池中的线程称之为worker,线程池中的worker从任务队列中一个一个地取出G来执行,然后worker从workers(线程池)中的调度由操作系统来处理


线程池这种方案有效的缓解了创建和销毁的时间开销问题,但又引入了另一个问题:系统调用时产生的阻塞情况

存在以下情况


如果worker发生了系统调用,那么操作系统会将该worker置于阻塞状态,也就是worker暂停处理任务了,这很明显不是我们乐意见到的。

更严重的是,如果上面所说的情况大量出现,则意味着大量的worker在阻塞态,workers处理G的能力在下降,效率在降低😨


一种解决办法是增加线程数,但线程数无节制增加并不是一个好事情,因为还存在着线程争抢cpu,处理任务的能力存在上限,甚至可能因为切换进程的时间开销导致处理任务的能力下降。。。。

Groutine调度机制

为了解决以上的问题,golang提供了一种机制,使得线程数变少了,但是并发却没有变少,同时使得上下文的切换更为轻量,这就是goroutine调度机制,其中调度的goroutine

我们知道,在go中开启一个并发非常简单,只需要go func()即可,非常方便

这实际上采用的是创建了一个协程,这里的概念取自coroutine,它是一种比线程更加轻量的单位,用于并发处理

这里的机制就称为GMP模型

  • G(Goroutine): Go协程
  • M(Machine): 工作线程,在Go中称为Machine
  • P(Processor): 处理器(Go中定义的一个摡念,不是指CPU),包含运行Go代码的必要资源,有调度goroutine的能力

M必须要获取到P才可以处理任务,P有一个包含待处理G的队列,一个正在处理的G,P可以将G交给M进行处理

P的数量一般设置为CPU的核数,最大化CPU的效率

轮转调度

简单来讲就是P将队列中的G拿出来给M执行,执行了一段时间,将上下文保存,放回到队尾,然后再从队头取出一个新的G来执行,采取的先来先执行的原则

然后这里还设置有一个全局的G队列,用于存放从系统调用恢复的G,会被定期查看,防止有G在全局队列中饿死(无法被处理执行)

系统调用

上面提到了系统调用的问题,系统调用的时候相当于进入阻塞态,此时的M为M0,它会释放获取到的P,然后被另一个M(M1)获取到继续处理执行G

这里存在着一个M池用于存放闲置的M,例如M0如果从系统调用中恢复了,那么M0会尝试获取一个P,如果有就继续工作执行原来的G,如果没有空闲的P,则直接将原来的G放入公共队列中,并进入M池沉睡

任务抢夺

如果此时P没有G可以来运行处理了,此时P就会先尝试查询一下全局队列,有的话就取全局队列的G来进行处理,如果全局队列也没有,那么它就会去抢夺其他P的队列中的G,并且很讲信用,每次只抢一半!!😎

影翼

文章作者

发表回复

textsms
account_circle
email

jinzh notes

Goroutine调度机制
前言 复习一下goroutine的调度机制,为实习面试做准备 线程池 传统的线程调度中,为了减高并发场景中少线程的创建和销毁的时间开销,采取一种比较独特的方式,线程池 维基百科: 线程过…
扫描二维码继续阅读
2022-02-24