std::semaphore是c++20提供的计数型同步原语,用于限制最多n个线程并发访问资源;需包含头文件,用std::counting_semaphore模板声明,构造时指定初始计数值,acquire()获取、release()归还单个许可,不支持超时或批量获取,跨平台实现存在兼容性差异。

std::semaphore 是 C++20 的同步原语,用来限制同时访问某资源的线程数量
它不是“锁”,也不是“条件变量”,而是一个带计数的、支持原子等待/通知的轻量级同步工具。核心作用就是:让最多 N 个线程能同时通过某个临界点,超了就得等。比如限制数据库连接池最多 5 个活跃请求,或控制 GPU 计算任务并发不超过 3 个。
怎么创建和使用 std::semaphore(C++20 起)
必须包含 <semaphore></semaphore>,且编译器需支持 C++20(GCC 11+、Clang 13+、MSVC 19.30+)。构造时传入初始计数值,通常等于你允许的最大并发数:
#include <semaphore>
#include <thread>
#include <vector>
std::counting_semaphore<10> sem{5}; // 最多 5 个线程能 acquire()
void worker(int id) {
sem.acquire(); // 阻塞直到有许可
// ... 使用受保护资源
sem.release(); // 归还许可
}
-
std::counting_semaphore<n></n>模板参数N是最大可存储的计数值(防止溢出),不是初始值;初始值由构造函数参数决定 - 不要用
std::binary_semaphore替代计数场景——它只支持 0/1,acquire()后必须配对release(),否则会死锁 - 如果线程在
acquire()前被异常中断(比如抛异常),记得用 RAII 封装,否则许可可能永久丢失
常见错误:acquire() 不是 try_acquire(),没超时机制
标准 std::semaphore 的 acquire() 是无条件阻塞的,一旦调用就一定会等到许可可用。这在某些场景下很危险:
- 持有其他锁时调用
acquire()→ 可能导致死锁(比如先锁了 mutex,再等 semaphore) - 需要响应取消或超时 → 标准库不提供
try_acquire_for()或try_acquire_until(),得自己结合std::condition_variable+ 计数器模拟,或改用第三方库(如 Boost.Semaphore) - 误以为
acquire(2)存在 —— 实际上acquire()永远只取 1 个许可;要一次拿多个,得循环调用或用try_acquire()判断剩余数
Windows 和 macOS 上的兼容性现实
虽然 C++20 标准定义了 std::semaphore,但底层实现依赖 OS 原生信号量(Linux 的 sem_t、Windows 的 CreateSemaphore)。问题在于:
立即学习“C++免费学习笔记(深入)”;
- MSVC 在 Windows 上目前(VS 2022 17.8)仍用用户态自旋+条件变量模拟,高竞争下性能明显差于系统原生信号量
- macOS 的 libc++ 尚未完全实现
<semaphore></semaphore>(截至 Xcode 15.4),链接会失败,必须用 Apple 提供的dispatch_semaphore_t替代 - 跨平台项目若需稳定行为,建议封装一层抽象接口,运行时根据平台选择 std::semaphore / dispatch_semaphore / pthread_sem_t
真正难的从来不是“怎么写 acquire”,而是“什么时候不该用它”——比如资源释放顺序不确定、许可归还不成对、或者压根该用 std::shared_mutex 控制读写并发。这些地方一错,调试起来比死锁还隐蔽。










