协程池用于控制并发安全:每个goroutine至少占2KB栈空间,无节制启动易致内存耗尽或压垮下游;通过固定worker数量限制并发上限,并用chan实现任务队列与复用。

为什么不能直接用 go func() 而要搞协程池?
因为无节制地起 go func() 容易耗尽内存或压垮下游服务:每个 goroutine 至少占 2KB 栈空间,上万并发时就是几十 MB;更关键的是,如果任务本身有 IO 或依赖外部资源(比如数据库连接、HTTP 客户端),不加限制会导致连接打满、超时激增、错误率飙升。
协程池本质是「限制并发数 + 复用 goroutine」,不是为了减少 goroutine 数量,而是控制它在安全水位内运行。
worker 模型怎么搭?用 chan 做任务队列最轻量
核心结构就三样:jobs 输入通道(接收任务)、固定数量的 worker goroutine(从 jobs 取任务执行)、done 通道(通知任务完成)。不需要第三方库,标准库足矣。
-
jobs设为带缓冲通道(比如make(chan func(), 1000)),避免提交任务时阻塞调用方 - 启动 N 个
worker:每个都跑一个for job := range jobs { job() }循环 - 任务函数类型统一为
func(),简单场景够用;需要传参就用闭包封装,比如func() { doWork(x, y) } - 别忘了关
jobs通道来退出所有 worker——但通常池子长期运行,这步可延后
怎么控制最大并发数?靠启动时固定的 worker 数量
并发上限 = 启动的 worker 数量,和 jobs 缓冲区大小无关。后者只影响「任务能攒多少」,前者才决定「同一时刻最多几个在跑」。
立即学习“go语言免费学习笔记(深入)”;
示例代码片段:
type Pool struct {
jobs chan func()
}
func NewPool(maxWorkers int) *Pool {
p := &Pool{
jobs: make(chan func(), 1000), // 缓冲区只是防提交卡住
}
for i := 0; i < maxWorkers; i++ {
go func() {
for job := range p.jobs {
job()
}
}()
}
return p
}
func (p *Pool) Submit(job func()) {
p.jobs <- job // 如果缓冲区满,这里会阻塞——你得自己决定是否丢弃或重试
}
实际用的时候最容易漏哪几件事?
不是写完 Submit 就完事了。真实场景下这几个点常被跳过:
- 任务 panic 会导致 worker 退出,整个池子悄悄少一个并发能力——必须在
job()外层加recover - 没有任务超时控制:某个
job卡死(比如 HTTP 请求没设 timeout),会一直占着这个 worker - 池子没提供等待全部任务完成的机制,比如想等所有
Submit的任务跑完再 exit,得自己加sync.WaitGroup或done通道 - 忘记限制
jobs缓冲区大小,导致 OOM:100 万任务全塞进去,每个闭包哪怕只捕获几个 int,内存也扛不住
真正稳的协程池,90% 的代码都在处理这些边界,而不是调度逻辑本身。










