主框架

组件简述

M: machine的缩写。一个M代表一个内核线程,或称"工作线程"

P: processor的缩写。一个P代表执行一个Go代码片段所必需的资源(或称"上下文环境")

G: goroutine的缩写。一个G代表一个GO代码片段。前者是对后者的一种封装

组件功能简述

在这里插入图片描述

一个G的执行需要P和M的支持。一个M在与一个P关联之后,就形成了一个有效的G运行环境(内核线程+上下文环境)

每个P都会包含一个可运行的G的队列(runq)。该队列中的G会依次传递给与本地P关联的M,并获取运行时机

在这里插入图片描述

可以看到,M与KSE之间总是一对一的关系,一个M能且仅能代表一个内核线程

Go的运行时系统(runtime system)用M代表一个内核调度实体。M与KSE之间的关联非常稳固,一个M在其生命周期内,会且仅会与一个KSE产生关联

相比之下,M与P、P与G之间的关联是易变的,它们之间的关系会在实际调度的过程中改变

  • 其中,M与P之间也总是一对一的,而P与G之间也会建立关联
  • 因为一个G终归会由一个M来负责运行,它们之间的关联会由P来牵线

M

概述

一个M代表了一个内核线程。在大多数情况下,创建一个M,都是由于没有足够的M来关联P并运行其中可运行的G

不过,在运行时系统执行系统监控或垃圾回收等任务的时候,也会导致新M的创建

M的结构

在这里插入图片描述

g0: 表示一个特殊goroutine。这个goroutime是Go运行时系统在启动之初创建的,用于执行一些运行时任务

mstartfn: 表示M的起始函数,这个函数其实就是在编写go语句时携带的那个函数

curg: 表示存放当前M正在运行的那个G的指针

p: 表示与当前M相关联的那个P

nextp: 表示与当前M有潜在关联的P。把调度器将某个P赋给某个M的nextp字段操作,称为对M和P的预联。运行时系统有时候会把刚重新启动的M和已与它预联的那个P关联在一起,这也是nextp字段的主要作用

spinning: bool类型的,它用于表示这个M是否正在寻找可运行的G。在寻找过程中,M会处于自旋状态。Go运行时系统可以把一个M和一个G锁定在一起,一旦锁定,这个M就只能运行这个G,这个G也只能由该M运行

M的运行

基础的运行

M在创建之初,会被加入全局的M列表(runtime.allm)中。这时,它的起始函数和预联的P也会被设置

最后,运行时系统会为这个M专门创建一个新的内核线程并与之相关联。

如此一来,这个M就为执行G做好了准备。其中,起始函数仅当运行时候系统要用此M执行系统监控或垃圾回收等任务的时候才会被设置

这里的全局M列表其实并没有什么特殊的意义。运行时系统在需要的时候,会通过它获取所有M的信息。同时,它也可以防止M被当作垃圾回收掉

M的优化

在新M被创建之后,Go运行时系统会对它进行一番初始化,其中包括对自身所持的栈空间以及信号处理方面的初始化

在这个初始化工作都完成之后,该M的起始函数会执行。注意,如果这个起始函数代表的是系统监控任务,那么该M会一直执行下去,而不会继续后面的流程

否则,在起始函数执行完毕之后,当前M将会与那个预联的P完成关联,并准备执行其他任务。M会依次在多处寻找可运行的G并运行之。有了M,Go程序的并发运行基础才得以形成

M的空闲时的状态

运行时系统管辖的M(runtime.allm中的M)有时候也会被停止,比如在运行时系统执行垃圾回收任务过程中

运行时系统在停止M的时候,会把它放入调度器的空闲M列表(runtime.sched.midle)。这很重要,因为在需要一个未被使用的M时,运行时系统会先尝试从该列表中获取

M是否空闲,仅以它是否存在于调度器的空闲M列表中为依据

M的数量设置

单个Go程序所使用的M的最大数量是可以设置的。Go程序运行时候会启动一个引导程序,这个引导程序会为其运行建立必要的环境

在初始化调度器的时候,它会对M的最大数量进行初始设置,这个初始值是10000.也就是说,一个Go程序最多可以使用10000个M

  • 这意味着,最多可以有10000个内核线程服务于当前的Go程序

可以调用标准库代码包runtime/debug中的SetMaxThreads函数,并提供新的M的最大数量

runtime/debug.SetMaxThreads函数在执行完成后,会把旧的M最大数量作为结果值返回

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部