带缓冲 channel 可实现 goroutine 数量硬限制,缓冲大小即最大并发数,本质是轻量信号量,避免资源失控。

用带缓冲 channel 实现 goroutine 数量硬限制
直接 go f() 启动成百上千个 goroutine,不是“高并发”,是资源失控。真正可控的并发,必须有明确的上限——这个上限由一个带缓冲的 sem channel 控制,缓冲大小就是最大同时运行数。
它不依赖任何第三方库,也不需要复杂状态管理,本质就是一个信号量:每次执行前 取令牌,执行完 <code>sem 归还。缓冲为 0 就是无缓冲(同步阻塞),缓冲为 N 就最多 N 个并发。
- 缓冲大小设太小(如 1):串行化,失去并发意义
- 缓冲大小设太大(如 1000):起不到限流作用,下游可能被压垮
- 不要把
sem声明在循环里——每个 goroutine 共享同一个实例 - 务必在 goroutine 内部做
defer func() { sem ,否则 panic 会导致令牌永远丢失
worker 函数必须处理 panic,否则池子会“漏”
一个未 recover 的 panic 会让整个 worker goroutine 退出,而该 goroutine 不再从任务 channel 中取新任务,但 sync.WaitGroup 计数已减,range 循环也不会继续——结果是:任务堆积、goroutine 数持续下降、runtime.NumGoroutine() 稳定但业务卡死。
这不是偶发 bug,而是池子设计的刚性要求:每个 worker 必须兜底。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 在 worker 主循环最外层加
defer func() { if r := recover(); r != nil { log.Printf("worker panic: %v", r) } }() - 别只 recover,还要考虑是否重试、记录错误、或通知监控系统
- 如果任务本身含外部调用(如 HTTP),记得传入
context.Context并检查ctx.Err(),避免死等
任务 channel 缓冲大小影响生产者行为
任务 channel 是生产者(submitter)和消费者(worker)之间的桥梁。make(chan Job, N) 的 N 决定了“队列深度”,直接影响 submit 是否阻塞:
- N = 0(无缓冲):submit 立即阻塞,直到有 worker 空闲——适合背压敏感场景(如实时流控)
- N > 0(有缓冲):submit 最多缓存 N 个任务,之后才阻塞——适合吞吐优先、允许短时积压的场景
- N 过大(如 10000):内存占用上升,且掩盖了 worker 处理慢的真实问题
- 关闭任务 channel 的时机必须明确:所有任务提交完毕后,由生产者主动
close(jobs),否则 worker 的for job := range jobs永远不会退出
WaitGroup 使用必须满足三个时序条件
sync.WaitGroup 是等待 worker 结束的最简方式,但它极其敏感于调用顺序。错一处,就可能提前返回、panic 或永久阻塞。
-
wg.Add(1)必须在go func() { ... }()之前调用,不能放在 goroutine 内部 -
wg.Done()必须在 goroutine 退出前执行,推荐写在defer中 - 主 goroutine 调用
wg.Wait()前,必须确保所有wg.Add已完成,且任务 channel 已关闭 - 不要复用同一个
wg实例跑多轮任务——每次都要新建或显式重置(Go 1.20+ 支持wg.Add()负值,但慎用)
最难察觉的问题是:任务 channel 关闭了,但某个 worker 因 panic 早退,导致 wg.Done() 没执行,wg.Wait() 永远卡住。所以 recover + Done 成对出现,不是可选项。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










