暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Golang深度解读:M

艳先生 2022-07-25
2263

Go语言将线程抽象成M(Machine),每个M都是可以独立运行的单元,M的数据结构中主要包含的是跟系统环境相关的字段。

Go语言将可执行的上下文保存在G(Goroutine)中,G的数据结构中主要包含跟执行环境相关的字段,包括寄存器值,栈地址、defer,panic的指针等等。

《Golang深度解读:g0》中,简单讲述了g0的基本信息和在调度中所负责的工作。最先了解g0,主要原因是g0才是真正意义上在线程上运行的逻辑,M在创建之后,系统环境相关的数据保存在M中,线程的执行数据保存在g0中,g0与普通用户协程共用相同的数据结构和计算逻辑,只是在内核调度过程中,需要区分g0与用户协程的部分边界。

worker m

Go语言程序在执行过程中会创建多个M,但并不跟P的数量完全匹配,跟P绑定使用的M一般视为Worker Thread(为了统一概念,后面统一称Worker M),即用户计算逻辑能使用到的线程,P数量的设置也直接影响用户协程的并行执行能力,关于P数量的设置可参考runtime.GOMAXPROCS()

Worker M的启动通常由runtime.wakep()
开始,在执行runtime.startm()
时,需要获取空闲状态的P和空闲状态的M,如果没有空闲状态的P,则不会继续创建流程,而如果没有空闲状态的M,则会执行runtime.newm()
创建新的M。

M结构的内存分配与g0的初始化就在runtime.newm()
中进行,函数runtime.newosproc()
是Go抽象出来的统一接口,主要用来创建系统线程,通过条件编译的方式,在不同的平台上有不同的实现方式,如:darwin上底层执行的是pthread_create
系统接口创建线程,而在windows上执行的是CreateThread
系统接口,创建线程的同时,会将已经初始化完成的M结构的指针传递到对应的创建线程的系统接口中,由线程的启动回调负责在新线程中做后续的处理,直到流程执行至runtime.mstart()
,新M的执行流程参考《Golang深度解读:g0》

m0

Worker M通常是由其他M通过结构初始化并访问系统调用,执行线程创建接口创建出来,最后经过runtime.mstart()
进入正常Worker M的计算逻辑,那第一个M是怎么来的呢?

在Go语言runtime包中,通过不同版本的汇编实现了不同平台上程序启动的main()
函数入口,然后执行runtime.rt0_go()
完成对Go程序的初始化,并使用全局定义的m0/g0初始化第一个M,并将runtime.main()
函数地址传入runtime.newproc()
创建第一个用户协程,再执行runtime.mstart()
进行正常后续流程。

接下来的流程就比较熟悉了,m0进入调度循环,并启动第一个用户协程runtime.main()
进行前期的初始化,再访问用户代码中的main.main()
正式执行用户代码逻辑。

注:m0也是正常的Worker M,只不过创建的流程不太一样

sysmon

Go语言中除了负责执行用户协程的的一组Worker M之外,还有一个独立的监控M,称之为sysmon。


    systemstack(func() {
    newm(sysmon, nil, -1)
    })

    sysmon在m0初始化完,执行第一个协程runtime.main()
    时,在runtime.main()
    函数中被创建,因为sysmon不直接服务于用户协程,因此没有绑定可执行的P,没有绑定P的M,所有的执行流程都在g0中。sysmon主要负责完成Go程序在执行期间的一些协同工作,如:处理系统调用阻塞(syscall)、用户协程执行时间过长(关于Go程序的抢占调度方案后面再细致的剖析),还有超时未poll network等等问题。

    template thread

    runtime.newm()
    的函数中,创建新的线程时,由于Linux之类的系统底层是通过类似fork的系统调用创建线程,当M处于locked状态或M是在CGO中创建,在fork时可能会复制很多不可预知的状态,于是使用到了template thread的处理机制,在不方便由当前M直接创建新M时,将创建任务传递到template thread中,让template thread负责创建新的M,并按正常流程启动。

    template thread由runtime.startTemplateThread()
    创建,在m0初始化完,执行runtime.main()
    时,在CGO的检查项中执行runtime.startTemplateThread()
    创建template thread,另一处是runtime.LockOSThread()
    中,并且template thread也是不绑定P的。


    文章转载自艳先生,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论