线程数过多,意味着操作系统会不断地切换线程,频繁的上下文切换就成了性能瓶颈.Go提供一种机制
可以在线程中自己实现调度,上下文切换更轻量,从而达到线程数少,而并发数并不少的效果,而线程中调度的就是Goroutine
调度器主要概念:
1.G:即Go协程,每个go关键字都会创建一个协程
2.M:工作线程
3.P:处理器,包含运行Go代码的必要资源,也有调度goroutine的能力
其中M必须拥有P才可以执行G中的代码,P含有一个包含多个G的队列,P可以调度G交由M执行
M是交给操作系统调度的线程,M持有一个P,P将G调度进M中执行.P同时还维护一个包含G的队列,可以按照一定的策略将G调度到M中执行
其中P的个数却决于,程序启动时CPU的核数,由于
Goroutine调度策略
队列轮转
P周期性的将G调度到M中执行,执行一小段时间,将上下文保存下来,然后将G放到队列尾部,然后从队列中重新取出一个G进行调度
除了每个P维护的G队列以外,还有一个全局的队列,每个P会周期性地查看全局队列中是否有G待运行并将其调度到M中执行,全局队列中G的来源,主要有从系统调用中恢复的G,之所以P会周期性地查看全局队列,防止全局队列中的G被饿死
系统调用
一般情况下M的个数会略大于P的个数,多出来的M将会在G产生系统调用时发挥作用
当G0即将进入系统调用时,M0将释放P,进而某个空闲的M1获取P,继续执行P队列中剩下的G.而M0由于陷入系统调用而被阻塞,M1接替M0的工作,只要P不空闲,就可以保证充分利用CPU.
其中M1的来源可能是M的缓存池,也可能是新建的.当G0系统调用结束后,根据M0是否获取到P.将会将G0做不同的处理:
1.如果有空闲的P,则获取一个P,继续执行G0
2.如果没有空闲的P,则将G0放入全局队列,等待被其他的P调度,然后M0将进入缓存池睡眠
工作量窃取
多个P中维护的G队列有可能是不均衡的
竖线左侧中右边的P已经将G全部执行完,然后去查询全局队列,全局队列中也没有G,而另一个M中除了正在运行的G外,队列中还有3个G待运行。此时,空闲的P会将其他P中的G偷取一部分过来,一般每次偷取一半。偷取完如右图所示