不能用 for range tasks { go handle(task) },因为会瞬间启动大量 goroutine 导致内存暴涨、调度卡顿甚至 OOM;应使用固定 worker 数量 + 带缓冲 channel 实现可控并发。

直接用 go handle(task) 启动协程处理批量任务,大概率会内存暴涨、被系统 OOM kill 或压垮下游服务;必须用固定数量 worker + 带缓冲 channel 实现硬限流,这是唯一可控的方案。
为什么不能 for range tasks { go handle(task) }
这种写法等于把并发控制权完全交给任务量——10 万条日志、10 万次 HTTP 请求,就会瞬间拉起 10 万个 goroutine。
- 每个 goroutine 默认栈至少 2KB,10 万协程光内存就吃掉 200MB+,GC 压力陡增
- 调度器需维护所有 goroutine 状态,数量失控后延迟毛刺明显
- 下游服务(如数据库连接池、HTTP client pool)通常有硬限制,瞬间打满直接拒绝服务
- panic 未 recover 会静默丢失,没人知道它属于哪次请求
jobs channel 该用带缓冲还是无缓冲
优先选带缓冲:make(chan Job, N),N 一般设为预期最大待处理任务数(比如 1000)。
- 无缓冲 channel(
make(chan Job, 0))会让提交方在没空闲 worker 时直接阻塞,对上游调用不友好,尤其在 HTTP handler 这类短生命周期场景里容易拖垮整个请求链路 - 带缓冲能吸收突发流量,避免生产者卡住,但缓冲区过大可能掩盖积压问题
- 无缓冲适合强背压场景(如实时流控),但要求你必须确保 worker 始终在线且响应及时
worker 怎么安全退出不 panic
核心顺序只有一条:close(jobs) → wg.Wait() → (可选)close(results)。错一步就可能死锁或 panic。
立即学习“go语言免费学习笔记(深入)”;
-
close(jobs)只能由生产者(主 goroutine)调用一次,worker 里关会 panic - worker 必须用
for job := range jobs或job, ok := 感知关闭,不能只用 <code>select单独监听而不判ok -
sync.WaitGroup的Add()要在启动 worker 前完成,Done()必须在每个 worker 退出前执行(包括defer) - 别依赖
range自动退出就省略清理——它不会等你写完日志、归还资源再走
pool size 设多少才合理
不是越大越好;通常设为 CPU 核心数 ×2~×4,再根据 IO 密集度微调。
- CPU 密集型任务(如图像转码):worker 数接近逻辑核数,避免频繁抢占
- IO 密集型任务(如 HTTP 请求、DB 查询):可设到逻辑核数 ×3~×4,利用等待间隙
- 不要动态增减 worker 数量——所谓“动态扩缩容”,实际意味着你要安全启停 goroutine、重分配未完成任务、处理中间状态,极易引入竞态和泄漏
- 突发流量该用前置限流(如
golang.org/x/time/rate)+ 缓冲队列挡掉
真正容易被忽略的是关闭时机和资源归属:close(jobs) 和 wg.Wait() 之间不能有逻辑依赖,也不能在 worker 仍在读取 results 时提前 close(results);否则收结果时会 panic。这步必须严格串行,没有捷径可绕。


















