Go调度器

线程数过多,意味着操作系统会不断地切换线程,频繁的上下文切换就成了性能瓶颈.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偷取一部分过来,一般每次偷取一半。偷取完如右图所示

相关推荐
妙码生花2 天前
从 PHP 到 AI + Golang,程序员自救转型手记(八):设计管理员模型、热重载配置
前端·后端·go
tyung3 天前
Go 手写 Wait-Free MPSC 无界队列:SwapPointer 实现多生产者无锁入队
后端·go
陈明勇4 天前
Go 1.26 新特性回顾:语言增强、工具升级与 Green Tea GC 默认启用
后端·go
妙码生花4 天前
从 PHP 到 AI + Golang,程序员自救转型手记(二):目录结构、初始化 GIT、设计并开发配置系统
前端·后端·go
leeyi4 天前
Deer-Go:字节 Deer-Flow 的 Go 移植,深度研究 Agent 全拆解
go·aigc·agent
Bolt5 天前
TypeScript 7.0 来了:当 tsc 用 Go 重写之后
javascript·typescript·go
Go_error5 天前
Datatypes:Go 轻松支持数据库JSON类型
后端·go
任沫6 天前
Agent之Function Call
javascript·人工智能·go
唐青枫7 天前
别再把 interface 当万能盒子:Go 接口从隐式实现到项目解耦
go
tyung9 天前
Go 手写有界 SPSC 环形队列:无 CAS、无锁、Cache 友好的无锁模型
后端·go