Итак, я потратил некоторое время на изучение предмета и теперь могу попытаться ответить на свой вопрос. Весь жизненный цикл программы оказался немного более сложным:
- Новые процедуры создаются в специальной программе под названием
g0
, которая является своего рода главной программой потока. Любой вызов go func
изменяет стек с текущей процедуры, из которой он был вызван, на g0
(это делается в proc.go:newproc
). - Когда создается процедура (в
proc.go:newproc1
), еестек (и / или программный счетчик ПК) построен так, что он выглядит так, как он был вызван функцией goexit
. Это сделано для того, чтобы гарантировать, что при завершении и возврате goroutine он возвращается к goexit
. - Когда вызывается
schedule
и выбирается запуск goroutine, функция execute
выполняет его (==переходит на свой адрес с помощью gogo
функции сборки). - После завершения процедуры она возвращает функцию
goexit
, реализованную в сборке. - Товызов функции сборки
proc.go:goexit1
(не уверен, зачем нужен этот дополнительный шаг сборки). - Функция
goexit1
изменяет текущий стек на g0
. Это делается с помощью вызова mcall
(«вызов машинного потока»), который выполняет любую функцию, полученную в аргументе. В этом случае функция, переданная в mcall
, имеет вид goexit0
. - Реализованный в сборке
mcall
переходит на адрес стекового фрейма (SP) g0
и выполняет CALL
до goexit0
. - Функция
goexit0
выполняется в контексте g0
. Он помещает завершенную процедуру в список бесплатных программ и освобождает свой стек, если он был ранее увеличен. - Затем
goexit0
снова вызывает schedule
, что выбирает запуск программы, поэтому мы возвращаемся кШаг 3.
Так что, похоже, здесь нет рекурсии. Сама запланированная программа никогда не вызывает schedule
: это делает специальная программа g0
. Я до сих пор не уверен, что все подробности я уловил, поэтому комментарии и дополнительные ответы приветствуются.