不能直接用 go func() 启动大量协程,因无并发限制会导致内存暴涨、调度压力大乃至 OOM;协程池通过固定 worker 数量+缓冲 channel 实现可控并发,需用 WaitGroup 安全关闭,并建议为 task 添加 context 超时控制。

为什么不能直接用 go func() 启动大量协程?
因为没限制数量的 go func() 会像开闸放水一样创建成百上千个 goroutine,内存占用飙升,调度器压力大,甚至触发 OOM 或被系统 kill。真实业务中(比如并发处理 HTTP 请求、批量读写文件、消费消息队列),必须控制并发数——这就是协程池存在的根本原因。
核心思路是:预启动固定数量的长期运行 worker,通过一个共享 channel 接收任务,避免反复创建销毁 goroutine 的开销。
- worker 数量通常设为 CPU 核心数 × 2~4,或根据 I/O 密集型程度微调
- 任务 channel 必须带缓冲(如
make(chan Task, 100)),否则提交任务时可能阻塞 producer - 不要在 worker 内部直接 recover panic——它会吞掉错误且无法通知调用方;应在 task 函数内处理或让 panic 向上传导并由上层捕获
如何用 channel + for-range 实现基础 worker pool?
最简实现只需要一个任务 channel 和一组常驻 goroutine,每个 worker 循环从 channel 读任务并执行:
type Task func()
type WorkerPool struct {
tasks chan Task
}
<p>func NewWorkerPool(workerCount int) *WorkerPool {
return &WorkerPool{
tasks: make(chan Task, 100), // 缓冲区防阻塞
}
}</p><p>func (wp *WorkerPool) Start() {
for i := 0; i </p><p>func (wp *WorkerPool) Submit(task Task) {
wp.tasks </p>
注意:range wp.tasks 在 channel 关闭后自动退出循环,所以 shutdown 需显式 close(wp.tasks);但关闭后不能再 submit,否则 panic:send on closed channel。
如何安全地停止协程池并等待任务完成?
直接 close(wp.tasks) 只能阻止新任务进入,已接收但未执行的任务仍会跑完,而正在执行的 task 无法中断。真正“等待所有任务结束”需要额外同步机制:
- 用
sync.WaitGroup记录活跃 task 数量:Submit 前wg.Add(1),task 执行完wg.Done() - Stop 方法先 close channel,再
wg.Wait()等待全部 task 返回 - 不要在 worker 中调用
wg.Done()——因为 range 循环结束后 worker 就退出了,而 task 可能还在执行;必须在 task 函数内部调用
典型错误是把 wg.Done() 放在 worker 的 for 循环末尾,导致 task 还没执行完 wg 就减了,Wait() 提前返回。
要不要加 context 控制单个 task 超时或取消?
要。基础版 pool 对单个 task 完全无感知,一旦某个 task 卡死(比如网络 hang、死循环),整个 worker 就被占住,后续任务排队等待。解决方式是在 task 层面支持 context.Context:
type Task func(ctx context.Context) error
<p>// Submit 时传入带 timeout 的 ctx
func (wp *WorkerPool) Submit(task Task, timeout time.Duration) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
wp.tasks </p>
这样每个 task 自己决定是否响应 cancel,worker 不感知上下文——既保持 pool 简洁,又赋予 task 级别的可控性。不过要注意:如果 task 内部没检查 ctx.Done(),超时依然无效。
真正难处理的是那些不支持 context 的第三方函数(比如老版本 http.Get),这时只能靠启动子 goroutine + select + channel 超时中转,但会增加复杂度和内存开销。











