直接用 goroutine 会因滥发导致内存耗尽或下游服务崩溃;worker pool 通过 channel + for-select 实现并发控制,以限制任务数而非加速,适用于不可控任务源。

为什么直接用 goroutine 会出问题
并发任务一多,go f() 滥发 goroutine 很容易耗尽内存或打爆下游服务。Go 运行时不会限制你起多少 goroutine,但系统资源是有限的——常见现象是 runtime: out of memory 或连接被拒绝(比如调用外部 API 频繁返回 503 Service Unavailable)。Worker pool 的核心目的不是“加速”,而是“控流”。
关键判断:只要任务来源不可控(如 HTTP 请求、消息队列消费、文件扫描结果),就必须做并发数限制,而不是靠 runtime 自行调度。
用 channel + for-select 实现最简 worker pool
标准模式是:一个输入 channel 接收任务,N 个 worker 从该 channel 取任务执行,无需锁、无共享变量。这是 Go 并发模型的自然表达,不是“模拟线程池”。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
jobs是chan Job,缓冲区大小建议设为预期峰值任务量(比如 1000),避免 sender 阻塞 - 每个 worker 是独立 goroutine,用
for job := range jobs持续取任务,channel 关闭后自动退出 - 启动 N 个 worker 后,用
close(jobs)通知所有 worker 停止(注意:必须在所有任务发送完毕后关闭) - 别用
sync.WaitGroup等待 worker 结束——range会在 channel 关闭后自然退出,wg 只需等所有任务发完
jobs := make(chan Job, 1000) for w := 0; w <h3>如何安全地获取 worker 执行结果</h3><p>如果任务需要返回值(比如 HTTP 请求响应、计算结果),不能让 worker 直接写共享 slice——竞态风险高,且难以对应任务与结果。正确做法是让每个 <code>Job</code> 带一个 <code>result chan 字段。</code></p>
- worker 执行完后,把结果发到该 task 自带的 channel,不与其他 task 交叉
- 主 goroutine 用
for i := 0; i 顺序收结果(或用 <code>select配合超时) - 避免用全局
chan Result:多个 worker 往同一个 channel 写,结果顺序和任务顺序不一致,后续处理逻辑容易错乱 - 如果任务可能 panic,worker 内必须 recover,否则整个 pool 会因一个 goroutine 崩溃而停止消费
什么时候该用第三方库(如 ants、pond)
自己手写 pool 足够应对 90% 场景。但遇到这些情况,建议换库:
- 需要动态伸缩 worker 数量(比如根据 CPU 使用率增减),
ants支持RunningWorkers()和Tune() - 任务有优先级,要按权重/紧急程度调度,
pond提供PriorityTask接口 - 要求精确的等待队列长度监控、拒绝策略(如
DiscardOldest)、熔断统计,这些都得重复造轮子 - 项目已引入
ants且团队熟悉其行为——统一技术栈比微小性能差异重要得多
别为了“看起来更专业”而引入依赖。一个 20 行的手写 pool,只要正确处理了 channel 关闭、panic 捕获、结果传递,就比配置复杂的三方库更可靠。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










