支持原子批量获取许可的计数信号量命名及基础原语实现问询
同步对象正式名称
这类支持一次性申领指定数量许可的同步原语,通用正式名称为 批量获取型计数信号量(Bulk-acquire Counting Semaphore),也常被称为资源信号量(Resource Semaphore) 或 加权信号量(Weighted Semaphore),Java的Semaphore.acquire(int permits)接口就是这类原语的标准工业实现。
你提到的逐次申领许可的朴素方案会出现死锁,核心原因是任务可能占用部分许可但未完成全部申领,导致资源碎片化且无法被其他任务利用。规避死锁的核心原则是:仅当剩余资源完全满足当前任务需求时,才一次性分配对应资源,否则任务直接进入等待队列,不占用任何部分资源。
基于基础同步原语的安全实现
方案1:互斥锁 + 条件变量实现(性能最优,无死锁)
需要维护的内部状态:
- 总资源容量
total - 当前可用资源量
available - FIFO任务等待队列(保证先到先得)
- 保护上述状态的互斥锁
lock - 资源释放时触发的条件变量
cond
核心接口逻辑:
- 资源申领
acquire(required)
// 加锁保护状态,保证操作原子性 lock.lock(); // 用while循环应对条件变量虚假唤醒 while (available < required) { // 任务加入FIFO等待队列,阻塞等待资源释放通知 cond.wait(lock); } // 一次性原子扣减全部所需资源 available -= required; lock.unlock();
- 资源释放
release(used)
lock.lock(); // 归还使用完成的资源 available += used; // 广播通知所有等待任务重新校验资源是否足够 // 优化方案:仅校验FIFO队首任务,满足条件则单独唤醒,减少无效唤醒开销 cond.broadcast(); lock.unlock();
该实现完全规避死锁:不存在持有部分资源的等待任务,FIFO队列保证先到先得,不会出现大任务长期饥饿的问题,并发性能优异。
方案2:基于普通计数信号量实现(逻辑简单)
如果仅能使用普通单许可计数信号量实现,可以通过增加准入锁避免半占用问题:
- 准备两个信号量:容量等于总资源量的普通计数信号量
resource_sem、初始值为1的二进制互斥信号量gate_sem - 资源申领逻辑:
// 先获取准入锁,保证同一时间只有一个任务执行申领操作 gate_sem.acquire(); for (int i = 0; i < required; i++) { resource_sem.acquire(); } gate_sem.release();
- 资源释放逻辑:
for (int i = 0; i < used; i++) { resource_sem.release(); }
该方案的缺点是申领阶段串行,高并发场景下吞吐量低于方案1,但逻辑简单可移植性强,无死锁风险。
内容的提问来源于stack exchange,提问作者William Luc Ritchie
相关产品推荐
相关产品推荐

