Go语言goroutine调度器初始化

news/2024/10/4 21:23:41

1、调度器初始化

调用点:src/runtime/asm_amd64.s:349  -> CALL runtime·schedinit(SB)

runtime/proc.go : 526

func schedinit() {
// raceinit must be the first call to race detector.
// In particular, it must be done before mallocinit below calls racemapshadow.//getg函数在源代码中没有对应的定义,由编译器插入类似下面两行代码//get_tls(CX)//MOVQ g(CX), BX; BX存器里面现在放的是当前g结构体对象的地址_g_ := getg() // _g_ = &g0
......//设置最多启动10000个操作系统线程,也是最多10000个Msched.maxmcount = 10000......mcommoninit(_g_.m) //初始化m0,因为从前面的代码我们知道g0->m = &m0
......sched.lastpoll = uint64(nanotime())procs := ncpu  //系统中有多少核,就创建和初始化多少个p结构体对象if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {procs = n //如果环境变量指定了GOMAXPROCS,则创建指定数量的p
    }if procresize(procs) != nil {//创建和初始化全局变量allpthrow("unknown runnable goroutine during bootstrap")}......
}

前面我们已经看到,g0的地址已经被设置到了线程本地存储之中,schedinit通过getg函数(getg函数是编译器实现的,我们在源代码中是找不到其定义的)从线程本地存储中获取当前正在运行的g,这里获取出来的是g0,然后调用mcommoninit函数对m0(g0.m)进行必要的初始化,对m0初始化完成之后调用procresize初始化系统需要用到的p结构体对象,按照go语言官方的说法,p就是processor的意思,它的数量决定了最多可以有都少个goroutine同时并行运行。schedinit函数除了初始化m0和p,还设置了全局变量sched的maxmcount成员为10000,限制最多可以创建10000个操作系统线程出来工作。

这里我们需要重点关注一下mcommoninit如何初始化m0以及procresize函数如何创建和初始化p结构体对象。首先我们深入到mcommoninit函数中一探究竟。这里需要注意的是不只是初始化的时候会执行该函数,在程序运行过程中如果创建了工作线程,也会执行它,所以我们会在函数中看到加锁和检查线程数量是否已经超过最大值等相关的代码。

runtime/proc.go : 596

func mcommoninit(mp *m) {_g_ := getg() //初始化过程中_g_ = g0// g0 stack won't make sense for user (and is not necessary unwindable).if _g_ != _g_.m.g0 {  //函数调用栈traceback,不需要关心callers(1, mp.createstack[:])}lock(&sched.lock)if sched.mnext+1 < sched.mnext {throw("runtime: thread ID overflow")}mp.id = sched.mnextsched.mnext++checkmcount() //检查已创建系统线程是否超过了数量限制(10000)//random初始化mp.fastrand[0] = 1597334677 * uint32(mp.id)mp.fastrand[1] = uint32(cputicks())if mp.fastrand[0]|mp.fastrand[1] == 0 {mp.fastrand[1] = 1}//创建用于信号处理的gsignal,只是简单的从堆上分配一个g结构体对象,然后把栈设置好就返回了
    mpreinit(mp)if mp.gsignal != nil {mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard}//把m挂入全局链表allm之中// Add to allm so garbage collector doesn't free g->m// when it is just in a register or thread-local storage.mp.alllink = allm// NumCgoCall() iterates over allm w/o schedlock,// so we need to publish it safely.atomicstorep(unsafe.Pointer(&allm), unsafe.Pointer(mp))unlock(&sched.lock)// Allocate memory to hold a cgo traceback if the cgo call crashes.if iscgo || GOOS == "solaris" || GOOS == "windows" {mp.cgoCallers = new(cgoCallers)}
}

从这个函数的源代码可以看出,这里并未对m0做什么关于调度相关的初始化,所以可以简单的认为这个函数只是把m0放入全局链表allm之中就返回了。

m0完成基本的初始化后,继续调用procresize创建和初始化p结构体对象,在这个函数里面会创建指定个数(根据cpu核数或环境变量确定)的p结构体对象放在全变量allp里, 并把m0和allp[0]绑定在一起,因此当这个函数执行完成之后就有

m0.p = allp[0]
allp[0].m = &m0

到此m0, g0, 和m需要的p完全关联在一起了。

初始化allp

下面我们来看procresize函数,考虑到初始化完成之后用户代码还可以通过 GOMAXPROCS()函数调用它重新创建和初始化p结构体对象,而在运行过程中再动态的调整p牵涉到的问题比较多,所以这个函数的处理比较复杂,但如果只考虑初始化,相对来说要简单很多,所以这里只保留了初始化时会执行的代码:

runtime/proc.go : 3902

func procresize(nprocs int32) *p {old := gomaxprocs //系统初始化时 gomaxprocs = 0
......// Grow allp if necessary.if nprocs > int32(len(allp)) { //初始化时 len(allp) == 0// Synchronize with retake, which could be running// concurrently since it doesn't run on a P.lock(&allpLock)if nprocs <= int32(cap(allp)) {allp = allp[:nprocs]} else { //初始化时进入此分支,创建allp 切片nallp := make([]*p, nprocs)// Copy everything up to allp's cap so we// never lose old allocated Ps.copy(nallp, allp[:cap(allp)])allp = nallp}unlock(&allpLock)}// initialize new P's//循环创建nprocs个p并完成基本初始化for i := int32(0); i < nprocs; i++ {pp := allp[i]if pp == nil {pp = new(p)//调用内存分配器从堆上分配一个struct ppp.id = ipp.status = _Pgcstop......atomicstorep(unsafe.Pointer(&allp[i]), unsafe.Pointer(pp))}......}......_g_ := getg()  // _g_ = g0if _g_.m.p != 0 && _g_.m.p.ptr().id < nprocs {//初始化时m0->p还未初始化,所以不会执行这个分支// continue to use the current P_g_.m.p.ptr().status = _Prunning_g_.m.p.ptr().mcache.prepareForSweep()} else {//初始化时执行这个分支// release the current P and acquire allp[0]if _g_.m.p != 0 {//初始化时这里不执行_g_.m.p.ptr().m = 0}_g_.m.p = 0_g_.m.mcache = nilp := allp[0]p.m = 0p.status = _Pidleacquirep(p) //把p和m0关联起来,其实是这两个strct的成员相互赋值if trace.enabled {traceGoStart()}}//下面这个for 循环把所有空闲的p放入空闲链表var runnablePs *pfor i := nprocs - 1; i >= 0; i-- {p := allp[i]if _g_.m.p.ptr() == p {//allp[0]跟m0关联了,所以是不能放任continue}p.status = _Pidleif runqempty(p) {//初始化时除了allp[0]其它p全部执行这个分支,放入空闲链表
            pidleput(p)} else {......}}......return runnablePs
}

这个函数代码比较长,但并不复杂,这里总结一下这个函数的主要流程:

  1. 使用make([]*p, nprocs)初始化全局变量allp,即allp = make([]*p, nprocs)

  2. 循环创建并初始化nprocs个p结构体对象并依次保存在allp切片之中

  3. 把m0和allp[0]绑定在一起,即m0.p = allp[0], allp[0].m = m0

  4. 把除了allp[0]之外的所有p放入到全局变量sched的pidle空闲队列之中

procresize函数执行完后,调度器相关的初始化工作就基本结束了,这时整个调度器相关的各组成部分之间的联系如下图所示:

 原文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/44283.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

龙哥量化:通达信空信号,可以买入翻倍的指标公式源码

如果您需要代写公式, 请联系我。 龙哥QQ:591438821 龙哥微信:Long622889{当多空线上穿0轴以后并且沿着45度向上运行时可视为有效突破,此时的信号可视为有效信号。信号出现在平台盘整期间,或者是小多头回调之后向上拉升之际,此时的信号最为有效,其它时间的信号要仔细辨别…

龙哥量化:通达信筹码操盘,筹码来的副图源码

如果您需要代写公式, 请联系我。 龙哥QQ:591438821 龙哥微信:Long622889 DRAWGBK(ISLASTBAR, RGB(60,60,60),RGB(0,0,0),0,0,0); 机构控盘区:160,COLORMAGENTA ,LINETHICK1; DRAWTEXT(ISLASTBAR, 机构控盘区,-- ←机构控盘区),COLORMAGENTA ; 主升浪:150,COLORRED ,LINETHIC…

私有化部署bitwarden密码管理器(一)——环境准备

碎碎念 现在各种网站和手机APP都要求设置密码和输入密码,每个人都要设置大量的密码。如果都设为统一密码,就存在一旦一个密码被攻破,其他密码都失效的情况。如果要分别设置为不同密码,那么记住密码又成了一个困难的问题。所以密码管理工具也就应运而生,比较有名的密码管理…

私有化部署bitwarden密码管理器(三)——nginx方式部署

仅有IP无证书的环境部署bitwarden密码管理器 虽然使用npm方式部署比较简单,但是很多小伙伴可能没有一台带域名国外vps,接下来介绍仅有IP的Vps,如国内的服务器无域名证书或者是本地无外网的环境如:虚拟机、NAS、树莓派等环境部署bitwarden密码管理器。 部署Nginx Nginx和Nginx…

龙哥量化:诺曼底防线副图指标公式源码

如果您需要代写公式, 请联系我。 龙哥QQ:591438821 龙哥微信:Long622889VAR1:=(HHV(H,13)-LLV(L,13));VAR2:=(HHV(H,13)-C);VAR3:=(C-LLV(L,13));VAR4:=VAR2/VAR1*100-70;VAR5:=(C-LLV(L,55))/(HHV(H,55)-LLV(L,55))*100;VAR6:=(2*C+H+L)/4;VAR7:=SMA((VAR3/VAR1*100),3,1);…

【PB案例学习笔记】-05 图片浏览器

写在前面 这是PB案例学习笔记系列文章的第5篇,该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习,提高编程技巧,以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码,小凡都上传到了gitee代码仓库https://gitee.com/xiezhr/pb-project…

Cursor是什么?基于ChatGPT代码编辑器的cursor如何使用?VS Code如何迁移到Cursor的步骤

Cursor 是一个基于 Visual Studio Code(VS Code)技术构建的高级代码编辑器,专为提高编程效率并更深度地整合 AI 功能而设计。它不仅继承了 VS Code 的强大功能和用户界面,还增加了专门针对 AI 支持的特色功能。Cursor 是 VS Code 的一个分支,这意味着它基于 VS Code 的代码…

abp项目

abp项目.net版本升级 系统基于abp框架,.net版本为.net core 2.1,升级之后为.NET 6.0 1.升级项目的目标框架和依赖包项目列表如下升级顺序如下 HPE_ProjectManagement_WebApi.Web (需要修改的东西最多) HPE_ProjectManagement_WebApi.Utility HPE_ProjectManagement_WebApi.C…