std::counting_semaphore是c++20引入的轻量级计数信号量,仅支持acquire()、release()和带超时的try_acquire_for(),不兼容posix/windows传统api;需编译器支持(gcc 11+/clang 12+/msvc 19.30+),模板参数为编译期非负整数上限,构造值不得超限,无跨进程能力,不可替代std::mutex。

std::counting_semaphore 在 C++20 中怎么用
C++20 标准库终于提供了 std::counting_semaphore,它就是你想要的信号量。但注意:它只支持 constexpr 构造、acquire()、release() 和带超时的 try_acquire_for()/try_acquire_until() —— 没有 wait() 或 post() 这类 POSIX 风格命名,别按旧习惯找。
典型用法是控制对有限资源的并发访问,比如限制最多 3 个线程同时写日志:
std::counting_semaphore log_gate{3}; // 初始计数为 3
void write_log() {
log_gate.acquire(); // 阻塞直到有许可
// ... 写日志
log_gate.release(); // 归还许可
}
-
std::counting_semaphore的模板参数是最大计数值(编译期常量),必须 ≥ 1,且类型为无符号整型(通常用std::ptrdiff_t) - 构造时传入的初始值不能超过模板参数上限,否则行为未定义(不是抛异常,而是 UB)
- 它不提供“当前剩余计数”的公开接口,没法做条件判断,只能靠 acquire/release 的原子性来协调
Windows 上用 CreateSemaphore 怎么避免 HANDLE 泄漏
在 Windows API 层面,CreateSemaphore 返回的是 HANDLE,必须配对调用 CloseHandle,否则每次创建都泄漏一个内核对象——这比内存泄漏更隐蔽,任务管理器里看不到,但系统句柄数会慢慢耗尽。
常见错误是在线程函数里直接 CloseHandle,或者忘记在异常路径中关闭:
- 把
HANDLE封装进 RAII 类(如struct semaphore_guard { HANDLE h; ~semaphore_guard() { if (h != INVALID_HANDLE_VALUE) CloseHandle(h); } }) - 不要在多线程中重复调用
CreateSemaphore创建同一个逻辑信号量;应由主线程创建并传入,或用静态局部变量 +std::call_once初始化 -
ReleaseSemaphore的第三个参数(输出旧值)一般传nullptr,除非真需要调试计数变化
Linux 下用 sem_init / sem_wait 怎么选 PTHREAD_PROCESS_PRIVATE
POSIX 信号量分两种:基于文件系统的命名信号量(sem_open)和进程内匿名信号量(sem_init)。多线程场景几乎总是用后者,但第二个参数必须设为 0(即 PTHREAD_PROCESS_PRIVATE),设成 1 是给进程间共享用的,线程间用会出错或未定义行为。
-
sem_init(&sem, 0, 3)—— 第二个参数是0,不是PTHREAD_PROCESS_PRIVATE宏字面量(虽然值相同,但语义上更推荐写0,避免头文件依赖) -
sem_wait被信号中断时会返回 -1 并设errno = EINTR,不自动重试;需手动循环检查 - 必须用
sem_destroy销毁,否则可能残留资源;且销毁前所有线程必须已离开sem_wait
自己手写简易信号量要注意哪些原子操作陷阱
如果目标平台不支持 C++20 或需深度定制(比如带优先级队列的信号量),得自己实现。核心是:计数器本身必须用 std::atomic<int></int>,但仅靠原子加减不够——因为 acquire 需要“检查-等待-递减”三步原子性,而这不是单条原子指令能完成的。
- 不能只靠
counter.fetch_sub(1):当值为 0 时,它仍会减成 -1,破坏语义 - 正确做法是用
compare_exchange_weak循环重试,或借助std::condition_variable+ 互斥锁模拟(但会损失 lock-free 特性) - 释放端(
release)可以简单用counter.fetch_add(1),但要注意溢出:若计数器类型是int,上限应人工限制,避免翻转 - 唤醒等待线程时,
notify_one()不保证唤醒的是等得最久的,也没有公平性保障;这点和系统原生信号量一致,不是 bug
真正难的不是计数逻辑,而是等待队列的线程安全维护和唤醒时机——稍有不慎就会死锁或丢失唤醒。生产环境建议优先用标准库或 OS 原语,手写只用于学习或极特殊约束场景。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











