sync.semaphore是go 1.21+标准库推荐的带权重、上下文取消和panic安全的信号量;需用int64初始化,acquire须检查error,release须defer保障,且ctx应贯穿全程。

用 sync.Semaphore(Go 1.21+)最稳妥
Go 1.21 起标准库自带 sync.Semaphore,是目前生产环境首选。它不是简单计数器,而是带权重、支持上下文取消、panic 安全的信号量实现。
- 初始化必须传
int64:比如限 8 并发,得写semaphore.NewWeighted(8),传8(int)会编译报错 -
sem.Acquire(ctx, 1)必须检查返回 error —— 若 ctx 已取消或超时,会立刻返回context.Canceled或context.DeadlineExceeded,此时不能执行业务逻辑 -
defer sem.Release(1)要写在 goroutine 最外层,哪怕doWork()panic,defer 仍会执行,令牌不会泄漏 - 别把
ctx只用在Acquire上:如果业务本身要发 HTTP 请求,把同一个ctx也传给http.Client.Do(),否则 Acquire 等到了,请求却卡死在 TCP 握手
用 chan struct{} 模拟信号量(兼容所有 Go 版本)
轻量、零依赖,适合简单场景,但极易翻车——漏释放一次,后续所有任务永久阻塞。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 声明必须带缓冲:
sem := make(chan struct{}, 5);写成make(chan struct{})(无缓冲)就失去限流意义 - 获取和释放必须严格配对:
sem 占位,<code> 归还;不能用 <code>len(sem)判断是否可进——它返回已占用数,可用数是cap(sem) - len(sem) defer func() { 是安全写法,但闭包变量要显式传参,比如 <code>go func(s chan struct{}) { s ,否则闭包捕获的是循环变量,可能全塞进同一个 slot- 别在
select里只写case 就开干——万一任务 panic,defer 还没注册,令牌就丢了
HTTP 请求并发限制,关键位置在哪
限流逻辑必须落在真正发起网络调用前,而不是 goroutine 启动时或 for 循环外。很多人把信号量放错位置,结果 QPS 照样打满下游。
- 正确位置:
http.NewRequest()之后、client.Do()之前;哪怕请求失败、超时、甚至 panic,defer 也能保证归还许可 - 错误示范:
for range urls { go func() { sem —— 这里 <code>sem 在 goroutine 外部,只是限制了启动速度,实际连接数仍不可控 -
http.Transport的MaxConnsPerHost不等于并发请求数:它控制复用连接池上限,和信号量是协作关系,不是替代关系;建议信号量设为业务目标并发数(如 10),MaxConnsPerHost设为略大值(如 15)
什么时候该换 worker pool,而不是只靠信号量
信号量只回答“最多几个同时跑”,不回答“谁来跑”“跑几次”“积压了怎么办”。一旦出现以下情况,就得升维:
- 需要拒绝新任务而非无限排队:信号量阻塞等待,worker pool 可配
ants.WithNonblocking(true)直接丢弃 - 要统计实时指标(当前运行数、排队中数量、平均耗时):裸信号量无法提供这些观测维度
- 任务执行时间差异极大,某些长期 hang 住(如第三方接口不响应):worker pool 可内置超时熔断,信号量只能等它自己结束
- 你已经在用
ants或类似库:它的NewPool(n)本质就是封装了信号量 + 预热 goroutine + panic 捕获,比手写更稳;注意别设n远小于任务量又不加任务队列缓冲,否则大量任务被静默丢弃
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










