万级并发直接用 go func() 会因调度、内存、文件描述符压力崩溃;应使用 ants 协程池并显式配置 worker 数、非阻塞模式和最大阻塞任务数。

直接用 go func() 启动万级并发一定会崩
不是协程太重,是 runtime 没法兜住调度、内存、文件描述符三重压力。上万个 go func() 会瞬间生成等量 goroutine,触发:panic: runtime: out of memory、too many open files、HTTP 超时陡增、P99 延迟跳变到秒级。
常见误判是“goroutine 才 2KB,肯定扛得住”——但 GC 扫描压力、调度队列长度、TCP 连接池耗尽、甚至 runtime.GOMAXPROCS 默认等于 CPU 核数,都会在万级并发下集体恶化。
- 真正需要的是“同时活跃处理 N 个任务”,不是“同时存在 N 个 goroutine”
- 裸
go func()相当于把背压甩给操作系统,Go runtime 不会自动限流 - 哪怕只跑一次批处理,没节制的并发也容易卡死本地开发机
ants 池比手写 channel 控制更稳,尤其超 5k QPS 场景
ants 是目前最成熟的 Go 协程池库(by panjf2000),它默认解决了手写易漏的四个关键点:动态伸缩、任务超时、panic 捕获、统计埋点。而纯 channel + select 控制在边界场景极易失控——比如任务 panic 后 worker 退出、等待队列无限堆积、无超时导致 goroutine 永久阻塞。
适用场景差异明显:
立即学习“go语言免费学习笔记(深入)”;
- 选
ants:需稳定支撑5k+QPS 的 HTTP 服务、批量消息消费、定时任务分发 - 选手写
channel:极简 CLI 工具、单次跑批且并发可控(如maxConcurrency = 1024)
实测 10k 并发下:ants 池比裸 go func() 内存低 40%,P99 延迟稳定在 3ms 内;手写 channel 若未加超时/熔断,延迟毛刺明显。
ants.NewPool() 必须显式设这 3 个参数
不设等于裸奔。哪怕只用标准库 sync.Pool + chan 组合,也得显式控制资源上限。
-
ants.NewPool(100):核心 worker 数,建议设为2 * runtime.NumCPU()起步 -
ants.WithNonblocking(true):任务提交失败直接丢弃(适合日志类、监控上报等可丢失场景) -
ants.WithMaxBlockingTasks(1000):等待队列上限,防 OOM;设 0 表示无限排队,慎用
错误写法:ants.NewPool(100) 不加其他配置 → 等待队列无上限,突发流量打满内存;正确写法:pool, _ := ants.NewPool(100, ants.WithNonblocking(true), ants.WithMaxBlockingTasks(1000))
Worker Pool 模式里,taskChan 缓冲区大小不能等于 pool size
很多手写协程池代码把 taskChan 设成带缓冲 channel,且容量等于 worker 数量,以为“刚好匹配”。这是典型误区:缓冲区等于 pool size 会导致新任务立刻阻塞,无法实现“任务排队+worker 复用”的设计本意。
正确做法是:
- 缓冲区要大于 pool size(如
make(chan func(), 200)配100worker),留出积压空间 - 或干脆用无缓冲 channel +
select配超时,靠WithMaxBlockingTasks或外部限流器控总量 - 若用
sync.WaitGroup管理生命周期,taskChan关闭前必须确保所有 worker 已退出,否则range会 panic
真正难的不是写出来,是让池子在 panic、超时、扩容缩容、长期运行时不泄漏、不卡死、不静默丢任务——这些细节,ants 已经帮你踩过坑。自己造轮子前,先看它怎么处理 recover() 和 context.Context 透传。


















