直接用 go func() 会崩是因为无节制创建导致调度器过载、内存暴涨,甚至触发 OOM;每个 goroutine 初始栈约 2KB,10 万个即超 200MB,且频繁创建销毁引发调度抖动,IO 或 CPU 密集型任务进一步加剧资源争抢。

为什么直接用 go func() 会崩
不是 goroutine 本身贵,而是没节制地创建会让调度器过载、内存暴涨,甚至触发 runtime: out of memory 或被系统 OOM killer 杀掉。每个 goroutine 初始栈约 2KB,10 万个就是 200MB+;更致命的是频繁创建/销毁带来的调度抖动——CPU 时间全花在切换上,真正干活的反而少。
- IO 密集型任务(如 HTTP 请求、数据库查询)看似“空闲”,但 goroutine 仍驻留内存并参与调度
- CPU 密集型任务(如 JSON 解析、加密计算)会抢满核数,再多协程只是排队等 CPU,徒增开销
- 无缓冲
chan作任务队列时,生产者一发就阻塞,根本压不住并发洪峰
jobs chan Job 缓冲区大小怎么设
缓冲区不是越大越好,也不是越小越安全,关键看任务到达节奏和 worker 处理速度的差值。设为 0(无缓冲)会导致生产者立刻阻塞,适合严格背压场景;设得过大(比如 10000)等于把内存压力从 goroutine 转移到 channel 底层 slice 上。
- 典型值:设为
workerCount * 2 ~ 5,例如 4 个 worker 配 16 容量缓冲区 - 如果任务提交方是批量写入(如读文件逐行投递),缓冲区可略大,防瞬间 burst
- 如果任务来源不可控(如 HTTP 接口直收),建议配合超时控制:
select { case jobs
如何安全关闭 WorkerPool 并等待完成
只 close(jobs) 不够——worker 退出后,主 goroutine 还得确认所有任务真正跑完,否则可能漏结果或 panic。
- 必须用
sync.WaitGroup记录活跃 worker 数:启动时wg.Add(1),workerdefer wg.Done() - 不能在
for job := range jobs循环里直接return,要让 range 自然结束(靠close(jobs)触发) - 如果用了结果通道
results chan Result,记得也 close 它,否则接收方可能永远阻塞 - 完整收尾顺序:
close(jobs)→wg.Wait()→close(results)(如有)
worker 数量设多少才合理
没有银弹值,硬写死 100 或 runtime.NumCPU() 都可能错。核心是区分任务类型,再结合观测调优。
立即学习“go语言免费学习笔记(深入)”;
- CPU 密集型:设为
runtime.NumCPU()或runtime.NumCPU() * 1.2,再多只会争抢 CPU - IO 密集型:可设到
runtime.NumCPU() * 3 ~ 5,因为大量时间在等网络/磁盘,不占 CPU - 混合型:从
runtime.NumCPU() * 2起步,上线后看go tool pprof的 goroutine profile 和 scheduler trace - 别忽略真实瓶颈:如果下游 DB 连接池只有 20,worker 开 100 毫无意义,反而引发连接拒绝
实际运行中,最常被跳过的一步是「关 channel 前确保所有提交已送达」——比如在 close(jobs) 后立刻 wg.Wait(),但仍有 goroutine 正在往 jobs 发送,会 panic。必须保证发送端全部退出或加锁保护。


















