Golang调度程序如何以及为什么在runtime / proc.go:execute中递归运行goroutine?

时间:2019-10-22 18:07:02

标签: go programming-languages

我正在尝试分解Go调度程序的工作方式,我在runtime/proc.go中看到的是:

  1. schedule函数调用execute来运行goroutine
  2. execute的注释明确表示此函数永不返回。它调用在汇编文件之一中定义的gogo函数。
  3. gogo函数跳转到新goroutine的第一条指令的地址。
  4. 此goroutine完成后,再次调用schedule函数,因此我们返回到步骤1。

如果我的理解是正确的,那么该方案如何避免堆栈溢出? 它是否与自动增加其大小的“无限”堆栈有关,还是我在这里丢失了一些东西?

1 个答案:

答案 0 :(得分:0)

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

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

因此,这里确实似乎没有递归。计划的goroutine本身从不调用schedule:这是通过特殊的goroutine schedule完成的。 我仍然不确定我是否捕获了所有详细信息,因此请多加评论和其他答案。