不能直接 go f() 而必须用 worker pool,因为无节制启动 goroutine 会导致内存耗尽、下游压垮、连接池打满、限流触发及错误难追踪;worker pool 通过任务队列、并发控制、统一错误处理和安全关闭机制保障稳定性。

为什么不能直接 go f() 而必须用 Worker Pool
因为瞬间起成百上千个 goroutine 会吃光内存、压垮下游、甚至触发 runtime: out of memory 或被系统 OOM killer 杀掉——每个 goroutine 至少占 2KB 栈空间,10 万并发就是 200MB 内存,还没算调度开销和文件描述符耗尽。
- HTTP 批量请求时,
go fetch(url)容易打穿 DNS 缓存、连接池或第三方 API 的限流(比如返回429 Too Many Requests) - 数据库批量插入时,无节制协程会挤爆
max_open_connections,导致大量dial tcp: i/o timeout - panic 未 recover 时,单个 goroutine 崩溃不会影响其他,但没人知道哪个任务失败了——Worker Pool 可统一捕获、记录、重试
jobs chan Job 该用带缓冲还是无缓冲
取决于你是否需要“背压”信号:无缓冲 channel(make(chan Job))会让提交方立即阻塞,适合强控节奏;带缓冲(make(chan Job, 100))能吞吐突发,但缓冲区满后仍会阻塞,且容易掩盖积压问题。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- IO 密集型任务(如 HTTP 请求)建议小缓冲,比如
make(chan Job, 50),兼顾吞吐与可控性 - 任务来源不可信(如用户上传的 CSV 文件解析),用无缓冲 +
select配超时,避免上游卡死 - 千万别设超大缓冲(如
10000)假装“高并发”,这只会把压力转移到内存和 GC 上
关闭 Worker Pool 时三步缺一不可
漏掉任何一步都可能死锁、panic 或结果丢失。顺序必须是:close(jobs) → wg.Wait() → close(results)(如果用了结果 channel)。
-
close(jobs)是通知所有 worker “不再有新任务”,让for job := range jobs自动退出 -
wg.Wait()确保所有 worker 真的执行完了最后一个任务,而不是刚退出循环就收工 - 如果提前
close(results),worker 还在往里发结果就会 panic:send on closed channel - worker 内部必须用
defer wg.Done(),否则 panic 时计数不减,wg.Wait()永远不返回
如何安全传递 context.Context 到 worker 中
别把 context.WithTimeout 的结果塞进闭包传进去——parent context 生命周期可能比 worker 长得多,造成 context 泄漏;也别在 submit 时硬绑 ctx,worker 无法感知取消。
- 定义
type Task struct { Ctx context.Context; Do func(context.Context) error } - worker 执行前加
select { case ,主动响应取消 - 若需传播 cancel,应在
Do内部调用childCtx, cancel := context.WithCancel(t.Ctx),执行完立刻cancel() - 不要用
sync.Pool管理 worker——它不提供队列、限流、超时,也不是为长期协程设计的
go func() { for range jobs },而是想清楚:这个池子要扛住什么流量、失败怎么归因、超时由谁控制、结果怎么不丢——这些决定了 jobs 缓冲多大、workerCount 设多少、要不要加 resultChan、甚至要不要支持动态扩缩容。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










