不能用 chan struct{} 代替 semaphore.weighted,因其不响应 ctx.done()、无超时机制、tryacquire 需手动实现且易漏释放;http handler 中客户端断开后 goroutine 仍阻塞占用许可,panic 或提前 return 更会导致许可永久泄漏。

为什么不能用 chan struct{} 代替 semaphore.Weighted
它不是语义等价的替代品,而是“看起来像”的陷阱。最直接的问题是:不响应 ctx.Done(),没有超时机制,TryAcquire 需手动实现且极易漏释放。HTTP handler 中常见翻车:客户端断开后,goroutine 还卡在 select { case sem: ... } 里,许可被占着不放,资源慢慢耗尽。更隐蔽的是 panic 后没 defer 释放,或提前 return 漏掉 ,导致永久阻塞。而 <code>semaphore.Weighted 在 Acquire 返回 error(如 context.Canceled)时,根本不占用许可,天然避免这类问题。
semaphore.Weighted.Acquire 必须检查 error 的真实原因
Acquire 不是“大概率成功”,失败即终止流程。它只返回两类 error:context.Canceled 或 context.DeadlineExceeded。忽略它们等于让 goroutine 永久挂起或资源泄漏。
- 错误写法:
sem.Acquire(ctx, 1)后不判断 err,直接进临界区 - 正确姿势:失败立刻返回,比如 HTTP handler 中写
http.Error(w, "too busy", http.StatusServiceUnavailable) - 别嵌套判断:
errors.Is(err, context.Canceled)多余,直接if err != nil即可 - Acquire 失败时,绝不可调用
Release—— 没拿到租约,释放就是未定义行为
Acquire 和 Release 的数量必须严格配对
底层只是原子增减一个 int64 计数器,没有任何校验。错配会立刻破坏状态:
-
Acquire(ctx, 3)后写三次Release(1)虽不 panic,但中间若 panic 或 return,极易漏掉某次释放 - 推荐写法:
defer sem.Release(3),确保一次归还全部 - 多放(如
Release(5))会导致计数器溢出变负,后续所有Acquire都立刻成功(虚高) - 少放(如只
Release(2))会让计数器卡死,新请求永远阻塞 - 跨 goroutine 调用
Release是危险操作:虽线程安全,但语义上破坏“租约归属”
初始化和权重参数的常见误用
semaphore.NewWeighted(n) 的 n 是最大“权重总和”,不是并发 goroutine 数量。传 int 而非 int64 在 Go 1.21+ 会编译失败;传 0 或负数直接 panic。
- 容量为 10 时,
Acquire(ctx, 5)成功后剩余为 5;再Acquire(ctx, 6)会阻塞,直到有其他 goroutineRelease - 权重不是“优先级”或“比例”,而是硬扣单位数:申请 3 就扣 3,不拆分、不找零
- 监控时
sem.CurrentCount()返回的是“已分配权重总和”,不是活跃 goroutine 数——混用不同权重时,值与直觉不符 - 信号量实例必须是全局或 server 级生命周期,不能按请求 new,否则限流失效
sem := semaphore.NewWeighted(10),而是保证每个 Acquire 都有对应且数量一致的 Release,且只在成功后触发。业务逻辑越复杂,越容易在 panic、return 分支、重试循环里漏掉这一环。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











