golang scheduler 如何以及为什么递归 运行 goroutines in runtime/proc.go:execute?
How and why does golang scheduler recursively run goroutines in runtime/proc.go:execute?
我正在尝试分解 Go 调度程序的工作原理,我在 runtime/proc.go 中看到的是:
schedule
函数调用execute
到运行一个goroutine
execute
的注释明确表示此函数永远不会 returns。它调用在其中一个汇编文件中定义的 gogo
函数。
gogo
函数执行跳转到新 goroutine 的第一条指令的地址。
- 这个goroutine完成后,
schedule
函数又被调用了,所以我们回到步骤1。
如果我的理解是正确的,那么这个方案是如何避免栈溢出的呢?
它与自动增加大小的 "infinite" 堆栈有关,还是我在这里遗漏了什么?
所以我花了一些时间研究这个主题,现在可以尝试回答我自己的问题了。整个 goroutine 生命周期变得有点复杂:
- 新的 goroutines 是在一个名为
g0
的特殊 goroutine 中创建的,它是线程的主 goroutine。对 go func
的任何调用都会将堆栈从调用它的任何当前 goroutine 更改为 g0
(这是在 proc.go:newproc
中完成的)。
- 创建 goroutine 时(在
proc.go:newproc1
中),它的堆栈(and/or 程序计数器,PC)以看起来的方式构造就像它被 goexit
函数调用一样。这样做是为了保证当 goroutine 完成并且 returns 时,它返回到 goexit
.
- 当调用
schedule
并选择一个goroutine到运行时,execute
函数执行它(==通过gogo
汇编函数跳转到它的地址).
- goroutine完成后,returns到
goexit
函数,在汇编中实现。
- 那个汇编函数调用
proc.go:goexit1
(不确定为什么需要这个额外的汇编步骤)。
goexit1
函数将当前堆栈更改为 g0
。这是通过调用 mcall
("Machine thread call") 来完成的,它会执行参数中接收到的任何函数。在这种情况下,提供给 mcall
的函数是 goexit0
.
- 汇编中实现的
mcall
,跳转到g0
的栈帧(SP)地址,执行一次CALL
到goexit0
。
goexit0
函数在 g0
的上下文中执行。它将一个完成的 goroutine 放在一个空闲 goroutine 列表中,如果之前增加了它,则释放它的堆栈。
- 然后
goexit0
再次调用schedule
,选择一个goroutine到运行,所以我们回到第3步。
所以这里确实似乎没有递归。调度的 goroutine 本身从不调用 schedule
:这是由一个特殊的 goroutine g0
完成的。
我仍然不确定我是否捕捉到了所有细节,因此欢迎评论和补充答案。
我正在尝试分解 Go 调度程序的工作原理,我在 runtime/proc.go 中看到的是:
schedule
函数调用execute
到运行一个goroutineexecute
的注释明确表示此函数永远不会 returns。它调用在其中一个汇编文件中定义的gogo
函数。gogo
函数执行跳转到新 goroutine 的第一条指令的地址。- 这个goroutine完成后,
schedule
函数又被调用了,所以我们回到步骤1。
如果我的理解是正确的,那么这个方案是如何避免栈溢出的呢? 它与自动增加大小的 "infinite" 堆栈有关,还是我在这里遗漏了什么?
所以我花了一些时间研究这个主题,现在可以尝试回答我自己的问题了。整个 goroutine 生命周期变得有点复杂:
- 新的 goroutines 是在一个名为
g0
的特殊 goroutine 中创建的,它是线程的主 goroutine。对go func
的任何调用都会将堆栈从调用它的任何当前 goroutine 更改为g0
(这是在proc.go:newproc
中完成的)。 - 创建 goroutine 时(在
proc.go:newproc1
中),它的堆栈(and/or 程序计数器,PC)以看起来的方式构造就像它被goexit
函数调用一样。这样做是为了保证当 goroutine 完成并且 returns 时,它返回到goexit
. - 当调用
schedule
并选择一个goroutine到运行时,execute
函数执行它(==通过gogo
汇编函数跳转到它的地址). - goroutine完成后,returns到
goexit
函数,在汇编中实现。 - 那个汇编函数调用
proc.go:goexit1
(不确定为什么需要这个额外的汇编步骤)。 goexit1
函数将当前堆栈更改为g0
。这是通过调用mcall
("Machine thread call") 来完成的,它会执行参数中接收到的任何函数。在这种情况下,提供给mcall
的函数是goexit0
.- 汇编中实现的
mcall
,跳转到g0
的栈帧(SP)地址,执行一次CALL
到goexit0
。 goexit0
函数在g0
的上下文中执行。它将一个完成的 goroutine 放在一个空闲 goroutine 列表中,如果之前增加了它,则释放它的堆栈。- 然后
goexit0
再次调用schedule
,选择一个goroutine到运行,所以我们回到第3步。
所以这里确实似乎没有递归。调度的 goroutine 本身从不调用 schedule
:这是由一个特殊的 goroutine g0
完成的。
我仍然不确定我是否捕捉到了所有细节,因此欢迎评论和补充答案。