go没有线程池,但可用goroutine+channel实现协程池以节流并发;核心是固定复用而非无限创建,需缓冲通道、worker容错、submit可退让、shutdown可等待。

Go 本身没有“线程池”概念,但用 goroutine + channel 模拟出的协程池,是生产中最常用、最可控的并发节流方案。它不是为了替代 Go 调度器,而是防止任务洪峰压垮内存或拖慢响应。
为什么不能直接用 go func() {} 处理大量任务
看似无害的一行 go task(),在高吞吐场景下会迅速暴露问题:
- 每秒提交 1000 个任务 → 瞬间启动 1000 个
goroutine,每个默认栈 2KB,仅栈就占用 2MB 内存(还不算堆分配) -
runtime.NumGoroutine()可能飙升到上万,GC 频率上升,调度延迟变大 - 若任务含阻塞操作(如未设 timeout 的 HTTP 请求),空闲
goroutine无法复用,资源持续堆积
协程池的核心价值,就是把“无限创建”变成“固定复用”。
用 channel + WaitGroup 实现最小可行协程池
不依赖第三方库,50 行内可跑通。关键在于:任务通道必须带缓冲,worker 启动后立即进入 range 循环监听,且关闭逻辑要和 WaitGroup 严格对齐。
示例关键片段:
type Pool struct {
taskChan chan func()
wg sync.WaitGroup
}
<p>func NewPool(size int) *Pool {
p := &Pool{
taskChan: make(chan func(), size), // 缓冲大小 = worker 数,避免 Submit 阻塞
}
p.wg.Add(size)
for i := 0; i </p><p>func (p *Pool) worker() {
defer p.wg.Done()
for task := range p.taskChan { // 注意:range 会自动在 channel 关闭后退出
task()
}
}</p><p>func (p *Pool) Submit(task func()) {
p.taskChan </p><p>func (p *Pool) Shutdown() {
close(p.taskChan)
p.wg.Wait()
}</p>
注意:make(chan func(), size) 的缓冲大小建议等于 worker 数量,否则 Submit 可能意外阻塞或丢失背压控制。
Submit 阻塞 vs panic 是常见坑点
协程池不是万能兜底,错误使用反而加剧问题:
-
Submit方法默认同步阻塞 —— 如果你没控制好任务速率,调用方线程会被卡住。解决办法:加超时 select 或提前做队列长度检查 - worker 内部未 recover,一个
panic会让整个 goroutine 退出,该 worker 永久失效。务必在task()外层包一层defer func() { recover() }() - 忘记调用
Shutdown()或在未处理完任务时关闭 channel,会导致部分任务丢失或range提前退出
真正稳定的协程池,worker 必须能容错,Submit 必须可退让,Shutdown 必须可等待 —— 三者缺一不可。
协程池的复杂性不在代码长度,而在边界条件:任务是否允许丢弃、失败是否重试、worker 是否支持动态伸缩、channel 关闭时是否有正在执行的任务……这些细节决定它能不能进生产环境,而不是跑通 demo。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











