首页 > 解决方案 > golang 调度程序如何以及为什么在 runtime/proc.go:execute 中递归运行 goroutine?

问题描述

我试图分解 Go 调度程序的工作原理,而我在runtime/proc.go中看到的是:

  1. 运行 goroutine的schedule函数调用execute
  2. execute明确表示此函数永远不会返回的注释。它调用gogo在其中一个程序集文件中定义的函数。
  3. gogo函数执行跳转到新 goroutine 的第一条指令的地址。
  4. 这个 goroutine 完成后,schedule再次调用该函数,所以我们回到第 1 步。

如果我的理解是正确的,那么这个方案是如何避免堆栈溢出的呢?它是否与自动增加其大小的“无限”堆栈有关,或者我在这里遗漏了什么?

标签: goprogramming-languages

解决方案


所以我花了一些时间研究这个主题,现在可以尝试回答我自己的问题。整个 goroutine 生命周期变得更加复杂:

  1. 新的 goroutine 是在一个名为 的特殊 goroutine 中创建的g0,它是一种线程的主 goroutine。任何调用都会go func将堆栈从调用它的任何当前 goroutine 更改为g0(在 中完成proc.go:newproc)。
  2. 当 goroutine 被创建(in proc.go:newproc1)时,它的堆栈(和/或程序计数器,PC)以一种看起来像是被goexit函数调用的方式构造。这样做是为了保证当 goroutine 完成并返回时,它会返回到goexit.
  3. schedule被调用并选择运行一个 goroutine 时,execute函数会执行它(== 通过gogo汇编函数跳转到它的地址)。
  4. goroutine 完成后,它返回goexit函数,在汇编中实现。
  5. 该汇编函数调用proc.go:goexit1(不知道为什么需要汇编中的这个额外步骤)。
  6. goexit1函数将当前堆栈更改为g0. 这是通过调用mcall(“机器线程调用”)来完成的,该调用执行在参数中接收到的任何函数。在这种情况下,提供给的函数mcallgoexit0
  7. mcall汇编中实现的 跳转到 的g0堆栈帧 (SP) 的地址并执行CALLgoexit0
  8. goexit0函数在 的上下文中执行g0。它将一个已完成的 goroutine 放入空闲 goroutine 列表中,如果之前增加了堆栈,则释放其堆栈。
  9. 然后再次goexit0调用schedule,它选择了一个 goroutine 来运行,所以我们回到第 3 步。

所以确实这里似乎没有递归。调度的 goroutine 本身从不调用schedule:这是由一个特殊的 goroutine 完成的g0。我仍然不确定我是否捕获了所有细节,因此感谢评论和其他答案。


推荐阅读