当前位置:网站首页>go调度原理
go调度原理
2022-08-08 09:51:00 【蓝胖子的编程梦】
计算机是如何表达一层层的函数调用的呢?它发明了一种叫做栈的结构,首先明确,栈依然是在内存上开辟的一段空间,这个空间保存了函数的参数,返回,以及一些寄存器的信息。
当每调用一次函数时,cpu会将函数的下一条指令压入栈中,待函数返回时,将这条指令取出然后存入pc寄存器。pc寄存器记录了cpu每次运行的下一条指令。
理解程序执行实际是一条条指令在执行,就能理解线程切换或者协程切换的原理,本质上就是将程序的pc寄存器,栈寄存器切换到一个新的地址,让程序逻辑由一个新的上下文开始执行。
g
指示协程,其实协程,线程,本质上都是一组数据结构,里面包含了其要执行的函数的地址。
其中g里还包含了执行上下文信息,上下文是为了协程切换后恢复现场的,执行上下文即程序运行时的栈寄存器,pc寄存器的值。
cpu很单纯的,只要你给它寄存器的值设置好特定的数值,它就按固定的规则从寄存器里取值,然后用运算逻辑单元进行计算。
m
go要并发执行程序,本质上还是用线程在执行。
而m可以认定为系统线程的代言人,其原因在于go runtime在开辟线程时,将m作为参数传给了线程。
这个线程执行mstart 方法,这里特别说明一下,mstart方法是不会return的方法,一个方法如何实现永远不会return,一个是for循环,一个是方法内部将pc寄存器的值跳转到其他地方继续执行了。
go即采用后者,在mstart 里执行schedule()方法找到可执行的g,因为g绑定了特定要执行的函数,所以把pc寄存器的值执行了g绑定的函数去了。
同时注意,在new一个系统线程时,将m存储在了系统线程的tls里,即线程局部变量,这样在后续g调用过程中,都可以通过线程局部变量去得到当前的m。
p
p为processor,m在调用schedule()方法的时候,是先找到与其绑定的p,再从p里获取可运行的g执行。
p是何时与m绑定的呢
newm 时传入的p,这里可能是从pidle空闲列表获取空闲p传入
p的初始化是在什么时候呢
是在程序启动的时候初始化,由runtime.GOMAXPROCS()控制初始化的大小。
调度关系图
分析重点
g,p,m 是如何初始化并且相互联系实现调度的?
go 程序启动流程
go依然是靠线程运行的,来看看第一个线程是如何与m0绑定的,以及程序调度是怎样开始的。
程序入口函数是用汇编写的。
m0 代表的是程序启动的第一个线程,即主线程,g0的作用其实是保存线程的栈信息,因为每个线程都会有自己的栈。
m0,g0是全局变量且用汇编进行的初始化。
然后依次调用os.init,schedinit 函数进行一些数据的初始化。
p的初始化就是在procresize()函数里完成的
我们日常程序中写的main方法,实际上是runtime启动了一个协程(go func()关键字实际上经过汇编会变成newproc函数)去执行的。
通过newproc 能将主协程 main goroutine 加入到process里,然后调用mstart 方法可对p里的g进行调度。而此时p里的g就是main goroutine。
goroutine 如何调度的
G视角的调度
着重分析g进行调度切换的地方。
1,进行系统调用会发生调度切换,此时m会阻塞,与m绑定的p会与m进行解绑操作,等待监控线程sysmon 对p进行新的绑定。
2,进行select,channel ,sleep,或者net io 时,会发生调度,runtime一般会执行gopark方法,将协程与m进行解绑,让m重新调度另外可执行的g。
3,发生抢占以及执行Gosched()方法时,此时会主动让出执行权限,g会加入到全局队列p里(p分为本地局部队列,每个m绑定的p都有自己的局部队列,p还有一个全局队列,供所有p所共享),然后m重新执行调度函数。
M视角的调度
着重分析了sysmon 监控线程,这个是在程序启动后会开启的一个线程,用于监控长时间运行或者发送阻塞的p,来让它们重新发生调度。
监控线程是在一个for循环里执行的,首先检查epoll中已经准备就绪的文件描述符,如果有值,顺带取出其关联的glist,然后加入到runq(可执行队列,其实就是p队列)里。
然后执行retake方法对发生阻塞或者长时间运行的p进行重新调度。
注意看startm 方法,这个是对释放的p进行新的线程绑定的方法,starm首先会从midle列表去读取空闲的m,如果没有的话,就会用newm()方法新建m,然后绑定p进行调度。
所以,注意m是不会被销毁的,只会处于休眠的状态,如果程序发生大量的系统调用,将会导致p会不断地handoff(释放),然后调用starm()方法创建新的线程去执行,这是极其可能导致生产故障的地方。
P 视角的调度
参考文献
https://blog.tianfeiyu.com/source-code-reading-notes/go/golang_gpm.html
https://gitbook.coder.cat/function-call-principle/content/function-stack-frame.html
边栏推荐
猜你喜欢
随机推荐
一个用来装逼的利器
LVS负载均衡群集及NAT模式群集
67:第五章:开发admin管理服务:20:开发【解冻/冻结用户,接口】;(用户状态变更后,需要刷新用户状态,即变更用户会话信息:我们一般通过“删除redis中会话信息,强制用户重新登录“来做的;)
「控制反转」和「依赖倒置」,傻傻分不清楚?
实战项目:瑞吉外卖开发笔记
shell脚本知识记录
【数学知识】—— 质数/约数
巧用Prometheus来扩展kubernetes调度器
使用.NET简单实现一个Redis的高性能克隆版(三)
你一定要看的安装及卸载测试用例的步骤及方法总结
蔚来杯2022牛客暑期多校训练营6 ABGJM
Multi-scalar multiplication: state of the art & new ideas
ACWing 198. Antiprime Problem Solution
分门别类输入输出,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang基本数据类型和输入输出EP03
Forward Propagation and Back Propagation
C# - var 关键字
干货 | Oracle数据库操作命令大全,满满的案例供你理解,收藏!
Web优化躬行记(6)——优化闭环实践
正确使用灯光 安全文明出行
FRED应用:TMT MOBIE成像光谱仪的概念设计阶段杂散光分析