为什么 Goroutine 的堆栈是无限的:
Goroutine 的主要特征之一是它们的成本;他们很便宜
根据初始内存占用量创建(而不是 1 到 8
兆字节(传统的 POSIX 线程)并且它们的堆栈增长并且
根据需要收缩。这允许 Goroutine 以单个
4096 字节堆栈,可根据需要增长和缩小,而不会出现以下风险
永远用完。
然而,直到现在我还没有透露一个细节,它与
意外使用递归函数造成严重内存占用
操作系统耗尽,也就是说,当新堆栈
需要页面,它们是从堆中分配的。
当你的无限函数继续调用自身时,新的堆栈页面
从堆中分配,允许函数继续
一遍又一遍地调用自己。很快堆的大小
将超过您计算机中的可用物理内存量
哪个点交换很快就会使您的机器无法使用。
Go 程序可用的堆大小取决于很多因素
事物,包括 CPU 的架构和操作
系统,但它通常表示超过的内存量
你机器的物理内存,所以你的机器很可能会交换
在你的程序耗尽它的堆之前就大量地加载。
ref: http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite
空循环:
for{
}
使用 100% 的 CPU 核心,等待某些操作,具体取决于您可能使用的用例:
- sync.WaitGroup
like this
- select {}
like this
- 频道
- time.Sleep
是因为生成的 goroutine 堆栈大小非常小(2Kbytes),
主 Goroutine 更大吗?
不,你可以尝试这两个示例,看看 goroutine 的堆栈限制是相同的:
一个主 goroutine围棋游乐场,
尝试第二个 goroutine围棋游乐场:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
go run()
wg.Wait()
}
func run() {
s := &S{a: 1, b: 2}
fmt.Println(s)
wg.Done()
}
type S struct {
a, b int
}
// String implements the fmt.Stringer interface
func (s *S) String() string {
return fmt.Sprintf("%s", s) // Sprintf will call s.String()
}
Go Playground 上的两个输出是相同的:
runtime: goroutine stack exceeds 250_000_000-byte limit
fatal error: stack overflow
在 PC 上输出8 GB
RAM:
runtime: goroutine stack exceeds 1_000_000_000-byte limit
fatal error: stack overflow