go 1.21+ 推荐用 sync.semaphore 控制并发,需传 int64 初始化、检查 acquire 错误、defer release 保证 panic 安全;轻量场景可用带缓冲 chan struct{} 模拟,避免用 len(chan) 判断空位。

用 sync.Semaphore 控制并发(Go 1.21+ 推荐)
Go 1.21 起标准库自带 sync.Semaphore,是目前最稳妥的并发数控制方式。它本质是带权重的信号量,支持上下文取消、超时等待、panic 安全释放,避免手写计数器或 channel 时常见的漏释放、死锁问题。
常见错误包括:传入负数或零给 semaphore.NewWeighted();忘记检查 Acquire() 返回的 error;在 goroutine 内部用裸 sem.Release(1) 而非 defer —— 一旦 panic 就永远卡住。
-
sem := semaphore.NewWeighted(int64(n))初始化时必须传int64,比如3要写成int64(3) - 每次启动前调用
sem.Acquire(ctx, 1),若返回 error(如context.Canceled),应直接跳过任务 - 务必在函数最外层用
defer sem.Release(1),保证无论正常结束还是 panic 都能归还许可 - 如果业务逻辑本身也依赖
ctx(比如 HTTP 请求),请把同一个ctx传进去,别只用在Acquire上
用 chan struct{} 模拟信号量(轻量兼容方案)
不依赖外部包、Go 所有版本都可用,适合简单限流场景。核心是用带缓冲的 chan struct{} 当作“许可证池”,容量即最大并发数。
容易踩的坑:用 len(sem) 判断是否还有空位——它返回的是已占用数,不是可用数;误用 chan int 或 chan bool 浪费内存;defer func() { 写错位置导致 panic 后没执行。
- 初始化:
sem := make(chan struct{}, 10),缓冲大小就是最大并发数 - 获取许可:
sem (阻塞直到有空位) - 释放许可:
,必须放在 <code>defer中,且确保每个成功获取后都有对应释放 - 不要 close 这个 channel,否则后续读取会 panic;也不要用
select+default做非阻塞尝试,除非你明确要快速失败
HTTP 请求并发限制的关键位置
限流必须落在真正发起请求的那一刻,而不是 goroutine 启动时或循环里。很多人把 sem 放在 for 循环体外,结果只是限制了 goroutine 创建速度,实际并发连接数仍可能爆表。
典型错误还包括:把限流逻辑加在 http.Transport 的 MaxIdleConnsPerHost 上——那只是复用连接池,不等于并发请求数;或者在 http.Do() 外层加锁,导致整个 client 被串行化。
- 正确位置:每个 goroutine 内部、
http.NewRequest()之后、client.Do()之前 - 即使请求失败或超时,也要归还许可,
defer正好覆盖这个逻辑 - 别用
runtime.GOMAXPROCS控制并发数——它只影响 OS 线程数量,和你的业务并发完全无关
什么时候该考虑 worker pool 或第三方库
当需求超出“单纯限制数量”时,比如需要排队、优先级、重试、统计、自动伸缩,或者对 panic 捕获、监控指标有生产级要求,就该跳出单层信号量思路。
自己封装 worker pool 容易忽略队列缓冲上限,导致 OOM;直接上 ants 这类成熟库又可能引入不必要的复杂度。关键判断点在于:你的任务失败后是否需要重试?是否关心处理耗时分布?是否要防止突发流量压垮下游?
- 纯 IO 密集型任务(如批量 HTTP 请求),固定 size 的 worker pool(比如 20 个常驻 goroutine 消费任务 channel)比每任务启 goroutine + 信号量更可控
- 若需 panic 自动恢复、任务超时强制退出、运行中动态调参,
github.com/panjf2000/ants是经过大量生产验证的选择 - 但注意:任何池化方案都要配合理想的 worker 数量——通常设为 CPU 核心数 × 2 到 5 倍,IO 密集可更高,CPU 密集则不宜超过核心数
真正的难点不在“怎么写”,而在于“什么时候释放”。无论用 sync.Semaphore 还是 chan struct{},只要有一处 Release 或 漏掉,整个限流机制就会缓慢失效——起初只是变慢,后来彻底卡死。所以必须把释放逻辑绑定在最靠近业务入口的位置,并用 <code>defer 锁死。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











