Golang 运行初始化都做了什么

  1. 程序初始化
  • Go程序启动后,会先创建一个特殊的内核线程,称为 M0 (主线程)。M0是第一个也是唯一一个由Go运行时直接创建的线程,后续的M都由他按需创建。
  • 同时,Go运行时会初始化一个P。这个P会与 M0 关联(M0拥有这个P)。
  1. 创建 main.goroutine
  • 运行时随后会创建第一个G,也就是 main.goroutine。这个 G 的入口函数就是我们的 main.main
  1. 调度与执行
  • 新创建的 main.goroutine 不会被放入任何队列(既不是 全局队列也不是 P 的本地队列)。
  • 它会被直接放入当前 M0 的 g0 的 sched.next字段中(可以理解魏下一个要执行的G),或者设置为m0curg(当前正在执行的 g)。
  • m0 随即开始执行 main.goroutine
  1. main函数运行
  • M0 开始执行 main goroutine 的代码,即 main 包中的 main 函数。
  1. 后续G的创建
  • 在我们的 main 函数中,如果我们使用 go 关键字创建了新的 goroutine(例如 go foo()),这些新的G的调度策略就符合我们熟知的GMP规则了:
    • 优先放入本地队列:新创建的G会优先放入创建它的P的本地队列(LRQ)中,等待被调度。
    • 本地队列满了则放全局:如果当前P的本地队列已满(长度超过256),则会把本地队列的一半G和新创建的G一起打散,放入全局队列(GRQ)。

为什么第一个G不进入队列?

  • 效率最高:程序启动时,只有 M0 一个线程和一个P,没有其他G在竞争。直接让 M0 执行 main goroutine 是最高效的方式,无需经过复杂的入队、出队调度过程。
  • 必要性:main goroutine 是程序的起点,它必须被尽快执行。它要负责初始化程序、启动其他goroutine等关键任务。

M0 的具体作用与职责?

  1. 执行最初的运行时初始化

在 Go 程序的 main 函数甚至任何用户的代码执行之前,Go 运行时(Runtime)需要先准备好自己的工作环境。M0 就是这个“搭建舞台”的人。它负责:

  • 分配内存:为运行时所需的核心数据结构分配初始内存。
  • 初始化栈: 设置 M0 自己的系统栈(也称为 g0 的栈),这个栈用于执行调度、垃圾回收等关键的系统级任务。
  • 初始化堆: 初始化内存分配器(allocator)和垃圾回收器(GC)所需的基础设施。
  1. 创建并关联第一个 P (Processor)
  • M0 会根据 GOMAXPROCS 环境变量或默认值(CPU核心数)来创建第一个 P(逻辑处理器)。
  • 创建后,M0 会立即与这个 P 进行关联。记住,一个 M 必须持有一个 P 才能执行 Go 代码。
  1. 创建主 Goroutine (G) 并直接执行
  • M0 负责创建程序的第一个用户 Goroutine,即 main goroutine(其入口函数是 main.main)。
  • 正如上一个问题所讨论的,M0 不会将这个 G 放入任何队列,而是直接将它设置为当前要执行的任务并开始运行它。
  1. 启动调度循环
  • main goroutine 开始执行后,M0 本身并不会闲着。它会转而执行自己的调度循环(Schedule Loop)。
  • 在这个循环中,M0 会不断地从它所关联的 P 的本地运行队列、全局运行队列或其他地方(如网络轮询器)寻找可运行的 G,并执行它们。
  • 此时,M0 的行为就和程序运行期间创建的其他 M(工作线程)一样了,成为了普通调度大军的一员。
  1. 在需要时创建新的 M (Machine)
  • 在程序运行过程中,如果现有的 M 都在繁忙、P 的队列中有可运行的 G、且未达到系统线程数上限,运行时就需要创建新的操作系统线程来帮忙。
  • 这个创建新 M 的任务是由 M0 来完成的(更准确地说,是由 M0 的 g0 栈来执行创建逻辑)。新创建的 M 会从空闲 M 列表开始,准备接手一个 P 并开始工作。

关键特性与区别

  • M0 是特殊的:它是唯一一个不是由 Go 运行时本身通过常规线程创建机制(如 pthread_create)生成的 M。它随着进程的启动而存在。

  • M0 的 g0:每一个 M 都有两个重要的 G:

    • curg:当前正在执行的用户 Goroutine(例如 main goroutine 或 go foo() 创建的 G)。

    • g0:一个特殊的系统 Goroutine,它拥有一个更大的栈,用于执行调度、垃圾回收、栈增长等昂贵的系统级任务。M0 的 g0 负责了最关键的初始化工作。

  • 与其它 M 的关系:你可以把 M0 看作是“鸡”,把其他 M 看作是“蛋”。先有 M0 这只“鸡”,然后由它孵出了其他所有 M 这个“蛋”。

总结

M0 是 Go 程序运行的起点和基石。它的核心作用就像一个项目的总工程师:

  • 搭建工地(初始化运行时环境)。
  • 准备好第一个工作站(创建并关联第一个 P)。
  • 派出第一个工人开始工作(创建并执行 main goroutine)。
  • 然后自己转变为调度员(开始运行调度循环)。
  • 并在需要时招募更多工人(负责创建新的 M)。
Licensed under CC BY-NC-SA 4.0
皖ICP备20014602号
Built with Hugo
Theme Stack designed by Jimmy