美文网首页
Go并发编程-线程模型(M)

Go并发编程-线程模型(M)

作者: 筑梦之队 | 来源:发表于2020-09-15 15:59 被阅读0次

    M(内核线程)
    一个M代表了一个内核线程。在大多数情况下,创建一个新M的原因是没有足够的M来关联P并运行其中可运行的G。不过,在运行时系统执行系统监控或垃圾回收等任务的时候,也会导致新M的创建。
    M的部分数据结构如图所示。


    M的部分数据结构

    M结构中的字段众多,这里只挑选了几个最重要的字段。其中:
    g0:表示一个特殊的goroutine。这个goroutine是Go运行时系统在启动之初创建的,用于执行一些运行时任务。
    mstartfn:表示M的起始函数,这个函数其实就是我们在编写go语句时携带的那个函数。
    curg:存放当前M正在运行的那个G的指针。
    p:指向与当前M相关联的那个P。
    mstartfn、curg和p最能体现当前M的即时情况。
    此外,字段nextp用于暂存与当前M有潜在关联的P。让调度器将某个P赋给某个M的操作由nextp字段控制,称为对M和P的预联。运行时系统有时候会把刚刚重新启用的M和已与它预联的那个P关联在一起,这也是nextp字段的主要作用。
    字段spinning是bool类型的,它用于表示这个M是否正在寻找可运行的G。在寻找过程中,M会处于自旋状态。这也是该字段名的由来。
    Go运行时系统可以把一个M和一个G锁定在一起。一旦锁定,这个M就只能运行这个G,这个G也只能由该M运行。标准库代码包runtime中的函数LockOSThread和UnlockOSThread,也为我们提供了锁定和解锁的具体方法。M的字段lockedg表示的就是与当前M锁定的那个G(如果有的话)。

    M在创建之初,会被加入全局的M列表(runtime.allm)中。这时,它的起始函数和预联的P也会被设置。最后,运行时系统会为这个M专门创建一个新的内核线程并与之相关联。如此一来,这个M就为执行G做好了准备。其中,起始函数仅当运行时系统要用此M执行系统监控或垃圾回收等任务的时候才会被设置。而这里的全局M列表其实并没有什么特殊的意义。运行时系统在需要的时候,会通过它获取到所有M的信息。同时,它也可以防止M被当作垃圾回收掉。

    在新M被创建之后,Go运行时系统会先对它进行一番初始化,其中包括对自身所持的栈空间以及信号处理方面的初始化。在这些初始化工作都完成之后,该M的起始函数会被执行(如果存在的话)。注意,如果这个起始函数代表的是系统监控任务的话,那么该M会一直执行它,而不会继续后面的流程。否则,在起始函数执行完毕之后,当前M将会与那个预联的P完成关联,并准备执行其他任务。M会依次在多处寻找可运行的G并运行它。这一过程也是调度的一部分。有了M,Go程序的并发运行基础才得以形成。

    运行时系统管辖的M(或者说runtime.allm中的M)有时候也会被停止,比如在运行时系统执行垃圾回收任务的过程中。运行时系统在停止M的时候,会把它放入调度器的空闲M列表(runtime.sched.midle)。这很重要,因为在需要一个未被使用的M时,运行时系统会先尝试从该列表中获取。M是否空闲,仅以它是否存在于调度器的空闲M列表中为依据。

    单个Go程序所使用的M的最大数量是可以设置的。Go程序运行的时候会先启动一个引导程序,这个引导程序会为其运行建立必要的环境。在初始化调度器的时候,它会对M的最大数量进行初始设置,这个初始值是10,000。也就是说,一个Go程序最多可以使用10,000个M。这就意味着,最多可以有10,000个内核线程服务于当前的Go程序。请注意,这里说的是最理想的情况;由于操作系统内核对进程的虚拟内存的布局控制以及大小限制,如此量级的线程可能很难共存。从这个角度看,Go本身对于线程数量的限制几乎可以忽略。

    相关链接:
    Go并发编程-线程模型
    Go并发编程-线程模型(M)
    Go并发编程-线程模型(P)
    Go并发编程-线程模型(G)

    参考资料:
    https://www.ituring.com.cn/book/tupubarticle/16048

    相关文章

      网友评论

          本文标题:Go并发编程-线程模型(M)

          本文链接:https://www.haomeiwen.com/subject/xikfyktx.html