我们来一步步拆解实际能达到的最小内存占用。
1. 每个 goroutine 的核心内存组成(最小情况)
| 部分 | 大小(字节) | 说明 |
| goroutine 结构体 (g) | ≈ 300–400 字节 | 包含调度信息、指针、状态等(精确值随版本小幅波动) |
| 初始栈(stack) | 2 KiB = 2048 字节 | 当前绝大多数平台的最小初始栈(_StackMin) |
| 其他运行时开销 | ≈ 100–300 字节 | 包含 defer 链表指针、panic 记录、gc bitmap 标记位等 |
| 单 goroutine 理论最低 | ≈ 2.4–2.8 KiB | 最干净的空循环场景下实际能看到的下限 |
关键点:初始栈就是 2 KiB(不是 2KB 有时被误写成 2048 字节),这是 Go 1.4 以来长期保持的值(少数提案讨论过改成 4 KiB,但截至 2026 年初主流版本仍为 2 KiB)。
2. 10 万个这样的 goroutine 最少占用多少?
计算两种典型现实值:
| 场景 | 每协程估算 | 10 万协程总计 | 备注 |
| 最理想情况(接近理论下限) | 2.4 KiB | ≈ 240–260 MiB | 极干净、无额外分配、无 hot/cold 分离、无 PGO、无动态栈调整 |
| 更现实的干净空转场景 | ≈ 3–4 KiB | 300–420 MiB | 包含少量运行时元数据、GC 扫描位、栈 guard page 对齐等 |
| 带 fmt.Println 的版本 | 5–10 KiB+ | 500 MiB – 1 GiB+ | 因为 println 涉及缓冲、反射、字符串常量等,会把栈撑大甚至 grow 一次 |
结论(最少占用):
不断输出 “hello world” 的 10 万个 goroutine,最小能做到 ≈ 240–280 MiB
(前提是使用最简代码、不带任何缓冲、用 print 而不是 fmt、关闭 race detector、纯 amd64/linux 环境)
3. 最简测试代码(尽量接近最小内存)
package main
import (
"runtime"
"time"
)
func main() {
for i := 0; i < 100_000; i++ {
go func() {
for {
println("hello world") // 用内置 println 比 fmt 省内存
// time.Sleep(time.Millisecond) // 注释掉会更“空转”
}
}()
}
// 让主协程不退出
print("created 100k goroutines, RSS ≈ ? \n")
runtime.GC()
time.Sleep(30 * time.Second)
}
编译运行后用以下方式观察(最接近“最小”值):
# RSS(常驻内存)通常最接近你关心的值
watch -n 1 "ps -o pid,rss,cmd -p $(pgrep -f your-binary) | tail -1"
# 或者用 pprof 查看更精确的 /memory/classes/heap/stacks:bytes
go tool pprof http://localhost:6060/debug/pprof/heap
4. 实际观测到的典型范围
- 纯 for { println(“hello world”) }:通常 320–480 MiB
- 如果把 println 去掉,只 for {}:可降到 240–320 MiB
- 如果改成 for { time.Sleep(time.Second) }:更接近 250–300 MiB
总结一句话:
10 万个只是不停输出 “hello world” 的 goroutine,最少能压到大约 240–280 MiB 左右(理想干净场景下),现实干净代码一般落在 300–450 MiB 区间。
