I’m trying to break down how the Go scheduler works, and what I’m seeing in runtime/proc.go is:
executeto run a goroutine
- The comment for
executeexplicitly says this function never returns. It calls
gogofunction defined in one of the assembly files.
gogofunction performs a jump to the address of the first instruction of a new goroutine.
- After this goroutine is completed, the
schedulefunction is called again, so we’re back to step 1.
If my understanding is correct, then how does this scheme avoid stack overflow?
Does it have something to do with “infinite” stacks that automatically increase their size, or am I missing something here?
So I spent some time researching the subject and can now try to answer my own question. The whole goroutine lifecycle turned out to be a bit more complex:
- New goroutines are created in a special goroutine called
g0, which is kind of a main goroutine of a thread. Any call to
go funcchanges the stack from whatever current goroutine it was called from to
g0(this is done in
- When the goroutine is created (in
proc.go:newproc1), its stack (and/or program counter, PC) is constructed in a way that it looks like it was called by
goexitfunction. This is done to guarantee that when goroutine completes and returns, it is returned to
scheduleis called and a goroutine is chosen to run, the
executefunction executes it (== jumps to its address via the
- After the goroutine has completed, it returns to
goexitfunction, implemented in assembly.
- That assembly function calls
proc.go:goexit1(not sure why this extra step in assembly is needed).
goexit1function changes current stack to
g0. This is done with a call to
mcall(“Machine thread call”), which executes whatever function is received in an argument. In this case the function supplied to
mcall, implemented in assembly, jumps to the address of
g0‘s stack frame (SP) and performs a
goexit0function is executed in the context of
g0. It puts a completed goroutine on a list of free goroutines, and frees its stack if it was previously increased.
scheduleagain, which chooses a goroutine to run, so we get back to step 3.
So indeed there seems to be no recursion here. The scheduled goroutine itself never calls
schedule: this is done by a special goroutine
I’m still not sure if I captured all the details though, so comments and additional answers are appreciated.