This is a creation in Article, where the information may have evolved or changed.
Original: Why is a goroutine's stack infinite?
Translator: YOUNGSTERXYF
Novice go programmers may stumble upon a quirky feature of the Go language---related to the size of a goroutine available stack space---. This is usually the result of a programmer inadvertently constructing an infinite recursive function call. To illustrate this feature, take the following code (somewhat deliberately designed) as an example.
Package MainImport "FMT"type S struct { a, b int}//String implements the FMT. Stringer Interfacefunc (s *S) String() string { return FMT.Sprintf('%s ', s) //Sprintf would call S.string ()}func Main() { s := &S{a: 1, b: 2} FMT.Println(s)}
If you run this program (which I do not recommend), you will find that your machine starts to swap frequently (without understanding swap, which can simply be understood as "export import of data between memory and hard disk"), and may no longer respond to operational events unless you press ^c in a timely manner before it can be undone. I know that everyone will first try to run the program in the playground of the Go website, so I'm ready for you.
Most programmers should encounter infinite recursion problems, but this is only fatal to their programs, not usually for their machines. So why is the go program different?
One of the main features of Goroutine is its overhead---in terms of memory footprint initialization, the overhead of creating a goroutine is very small (compared to the 1-8m bytes of a traditional POSIX thread), and the goroutine stack space is scaled up and down on demand. This allows a goroutine to start with a single 4096-byte stack space and then scale up and down on demand, without worrying about the risk of stack space exhaustion.
To implement this feature, the linker (5L,6L,8L) inserts a small segment of leading code $ ^1 $ at the beginning of each function, which detects whether the function requires less stack space than the currently available stack space. If greater than, the call runtime.morestack
assigns a new stack page (Stack page) $ ^2 $, copies the arguments passed by the function caller, and then returns control to the function that was originally called, so that the function can run safely. When this function exits, the operation is undone and the function return value is copied back to the stack frame of the function caller (stack frame), and the stack space that is no longer needed is freed.
Through this process, the stack space is like infinity, assuming that it does not consistently span the size boundaries of both stacks-often referred to as stack splitting (stack splitting), which should mean: The program executes to the function caller, The allocation of the pre-allocated stack space is almost exhausted, and the function caller constantly call other functions, so that each function call will need to allocate a new stack space, the function call after the end of the need to release the new allocated stack space, so the overhead accumulation is relatively large , the overhead of this stack space is also very small.
However, until now I have not disclosed a detail---careless use of recursive functions resulting in memory exhaustion, when the need for a new stack page, will be allocated from the heap (note: This sentence may be a bit problematic.) The goroutine should be allocated from the heap if the operating system is running out of stack size allocated by the GO program.
Since the infinite recursive function calls itself continuously, the new stack page will eventually need to be allocated from the heap. The size of the heap will soon exceed the available physical memory space of the machine, and by that time, swapping will soon cause your machine to become unusable.
The available heap size of the GO program relies on many things, including the machine's CPU architecture and operating system, but this is usually a value beyond the machine's physical memory, so the machine is likely to swap frequently before the program runs out of its heap space.
For Go 1.1, there has been a strong demand to increase the heap on the 32-bit, 64-bit platform, but this has somewhat worsened the problem, for example, your machine is unlikely to have 128gb$ ^3 $ of physical memory.
Finally, there are several unresolved issue (links, links) on this issue, and no solution has yet been found that will not affect the performance of programs written in general.
Comments
- Also applies to methods, although the method is implemented as the first parameter as a function of the method receiver, but there is no real difference in discussing how the segmented stack works in the Go language.
- Using the word page does not mean that only a fixed 4096 bytes are allocated, and if required, Runtime.morestack assigns a larger, multiple space in a page size.
- Due to a subsequent change in the Go 1.1 release cycle, the 64-bit Windows platform allows only 32Gb of heap size.
Translators Supplement related articles
- A trip down the (split) rabbithole
- Go language split Stack (top), go language split stack (bottom)
- Go on a stack and do God's horse?