跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Golang如何做goroutine池_Golang协程池优化教程【实战】

Go中无内置goroutine池,本质是固定数量goroutine+带缓冲channel实现任务复用;必须用带缓冲channel避免Submit同步阻塞;缓冲大小应≈峰值积压量×1.5,过小退化为同步,过大导致内存滞留和吞吐问题;生产环境建议select+default过载丢弃。 Go 里没有“goroutine 池”这个内置概念,所谓“池”,本质是用固定数量的
goroutine
+ 带缓冲的
chan
实现任务复用——不是复用协程本身(协程不能复用),而是避免为每个任务都新建一个。 为什么必须用带缓冲的
tasks
channel? 无缓冲
chan
是同步点:
Submit
会卡住,直到有空闲 worker 取走任务。高并发下极易阻塞调用方,尤其在 HTTP handler 中直接
pool.Submit(...)
就可能拖垮整个请求链路。 缓冲区大小 ≠ worker 数量,而应 ≈ 单次峰值积压量 × 1.5(例如每秒最多涌进 200 个任务,平均处理耗时 50ms,缓冲设 300 更稳) 过小(如
make(chan Task, 1)
)等于没缓冲,退化成同步调用 过大(如
make(chan Task, 10000)
)会让任务滞留内存,GC 回收延迟,且掩盖了消费者吞吐不足的问题 生产环境建议配合
select
+
default
做过载丢弃:
select { case p.tasks <- task: default: // 记录告警或返回错误 }
如何安全关闭池并等待所有任务完成? 直接
close(p.tasks)
不等于所有任务已执行完——worker goroutine 可能还在跑,
range
退出后就结束了,但最后几个任务可能刚被取出、尚未执行完毕。 不能只靠
close(p.tasks)
,必须用
sync.WaitGroup
或
errgroup.Group
跟踪实际执行完成 worker 内部要
defer wg.Done()
,且
wg.Add(1)
必须在
go worker()
前调用 关闭流程顺序不能错: 先停生产者(关闭 tasks chan),再
wg.Wait()
,最后才真正释放资源 别在
Close()
里直接
close(p.tasks)
后就返回——这会让调用方误以为“已清空”,实际任务还在飞 要不要给任务加
context.Context
? 不加,就是“甩出去不管”;加了,才能支持超时、取消、透传 deadline——这对 I/O 密集型任务(HTTP 请求、DB 查询)几乎是刚需。 立即学习 “ go语言免费学习笔记(深入) ”; 原始
type Task func()
无法响应取消;应升级为
type Task func(context.Context) error
worker 中必须用
select
监听
ctx.Done()
,并在收到时立即退出,避免任务“幽灵执行” 注意:不要把
context.WithTimeout
放在 Submit 侧统一包一层——那样所有任务共享同一个 deadline,不合理;应在每个任务创建时按需设置 若任务本身不含 I/O,纯计算,context 开销略增(少量字段拷贝),但换来的是可观察、可中断的能力,值得 最常被忽略的一点:worker goroutine 里的 panic 不会自动传播,也不触发
recover
,会导致该 goroutine 静默死亡,池子悄悄少掉一个 worker。上线前务必在 worker 主循环里加
defer func() { if r := recover(); r != nil { log.Printf("worker panic: %v", r) } }()
。

相关文章