c++oding="utf-8" ?>
std::counting_semaphore的初始值即为设计并发上限,构造时传入的整数(如3)表示最多3个线程能同时通过acquire();它不支持运行时重设上限,release可使计数超初始值但不改变逻辑上限,必须成对调用acquire/release并用raii防异常泄漏。

std::counting_semaphore 的初始值就是并发上限
它不提供运行时动态调整最大许可数的接口,count 只能增减、不能重设上限。构造时传入的整数就是硬性并发阈值,比如 std::counting_semaphore 表示最多 3 个线程能同时通过 acquire()。
常见误用是以为可以像信号量原语那样“初始化为 0 再手动释放”,但 C++20 的 std::counting_semaphore 要求构造参数 ≥ 0,且 release(n) 可以使计数超初始值(比如初始为 2,调用 release(5) 后变成 7),但这不改变逻辑上的“设计并发上限”——你仍需靠代码逻辑确保不会意外放行过多线程。
- 初始值设为
N,就代表你预期最多N个资源使用者并发执行 -
acquire()阻塞直到有许可;try_acquire()立即返回bool,适合非阻塞场景 -
release(k)增加k个许可,可用于“归还多个”或“预发放”,但过度release会导致实际并发超出设计值
典型用法:保护固定大小的资源池
比如有 4 个数据库连接,想限制最多 4 个请求并发访问,直接用 std::counting_semaphore 包裹关键操作即可:
std::counting_semaphore db_sem{4};
void handle_request() {
db_sem.acquire(); // 最多 4 个线程能到这里
// ... use db connection ...
db_sem.release(); // 归还一个许可
}
注意:必须成对出现 acquire/release,否则许可数失衡。如果操作可能抛异常,要用 RAII 封装(例如自定义 guard 类或用 std::scoped_lock 思路仿写)。
- 没配 RAII 时,异常路径容易漏掉
release(),导致死锁 - 不要在同一个线程多次
acquire()而不release(),会耗尽许可并阻塞自己 -
acquire()是原子等待,不忙等,底层通常基于 futex 或条件变量
和 std::binary_semaphore 的关键区别
std::binary_semaphore 是 std::counting_semaphore 的别名,但语义更严格:它只允许值为 0 或 1,release() 多次也只保留 1(即“唤醒至多一个等待者”),而 counting_semaphore 允许计数值累积。
所以如果你需要“允许多个线程进入,且允许批量归还”,必须用 counting_semaphore;如果只是互斥或简单通知,binary_semaphore 更轻量、语义更清晰。
-
binary_semaphore底层可能优化为单比特操作,counting_semaphore需要维护整型计数,开销略高 - 两者都不支持
timed_acquire,超时需配合std::condition_variable手动实现 - Windows 上部分标准库实现(如 MSVC)对
counting_semaphore的try_acquire_for支持不完整,慎用超时变体
跨平台兼容性与编译器要求
该类型是 C++20 特性,GCC 11+、Clang 12+、MSVC 19.30+ 才完整支持。老版本编译器会报 unknown type name 'counting_semaphore'。
若项目需兼容 C++17,可用 std::mutex + std::condition_variable 手动模拟,但要注意:手动实现易出错,尤其在 notify_one/notify_all 和虚假唤醒处理上。
- Clang 12 默认不启用 C++20 模式,需显式加
-std=c++20 - 某些嵌入式 STL 实现(如 libc++ 的最小配置)可能未包含
<semaphore></semaphore>头文件 - Linux 上依赖
sys/futex.h,musl libc 对 futex 支持较弱,部分发行版需换 glibc
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











