Semaphore是计数锁而非互斥锁,必须配对acquire()与release()(推荐finally块),漏释放会导致许可泄漏和线程阻塞;Java支持公平模式与超时,Python原生Semaphore功能有限,需自行封装或换用协程方案。

Semaphore 本质是计数锁,不是互斥锁;它不保证线程顺序,只限制并发数量。
为什么 acquire() 后必须配对 release()
漏掉 release() 会导致许可数永久减少,后续线程在 acquire() 处无限阻塞(尤其在异常路径中)。常见错误是只在正常流程里 release(),没覆盖 try/catch 或 finally。
- 务必把
release()放进finally块,或用 try-with-resources(Java)/with(Python 的threading.Semaphore不支持自动释放,得手写) - 不要依赖线程退出自动归还许可——
Semaphore不跟踪持有者,只看计数 - 调试时可调用
availablePermits()(Java)或检查内部计数(Python 需自行封装)验证是否泄漏
Java 中 Semaphore 的公平性陷阱
构造时传 true 启用公平模式,看似更“合理”,但实际会显著降低吞吐量:所有等待线程被塞进 FIFO 队列,即使新来的线程本可立刻抢到许可,也得排队。默认非公平模式更高效,但可能造成某些线程长期饥饿(不过对限流场景影响不大)。
- 除非业务明确要求“先到先得”(如预约系统),否则别开公平模式
-
acquireUninterruptibly()会忽略中断,慎用;多数场景应捕获InterruptedException并处理 -
tryAcquire(long, TimeUnit)可设超时,避免死等,适合有响应时间要求的服务
Python threading.Semaphore 没有超时和公平选项
Python 标准库的 threading.Semaphore 功能精简:只有 acquire() 和 release(),且 acquire() 默认阻塞、不可中断、不支持超时。这意味着一旦卡住,只能靠外部信号或进程重启。
- 需要超时?自己封装:用
time.time()+ 循环轮询acquire(blocking=False),但注意忙等损耗 CPU - 要公平调度?没法做——底层基于
threading.Condition,无队列控制能力 - 替代方案:改用
asyncio.Semaphore(协程场景),或引入第三方如curio/trio,它们提供更可控的调度语义
真正难的不是调用 acquire(),而是厘清“资源粒度”——一个 Semaphore 应该对应数据库连接池?HTTP 客户端实例?还是某个特定 API 的 QPS?粒度错了,限流就失效。这点比语法细节重要得多。










