c++20标准中不存在std::binary_semaphore,实际可用的是std::counting_semaphore,当n=1时语义等价于二元信号量,无锁、轻量、支持原子操作,是低延迟同步的推荐方案。

std::binary_semaphore 在 C++20 中根本不可用
它不是标准库的一部分——std::binary_semaphore 从未被纳入 C++20 或 C++23 标准。你看到的可能是某些编译器(如 MSVC 的实验性 std::experimental::binary_semaphore)或第三方库(比如 Boost.Thread)的扩展,也可能是误把 std::counting_semaphore 当成了 binary 版本。
真正可用、标准且轻量的是:std::counting_semaphore。它语义上等价于二元信号量,但名字不叫 binary。
- 标准只定义了
std::counting_semaphore<n></n>模板,N是最大计数值,必须是编译期常量 -
std::counting_semaphore就是你想要的“二元”行为:只能取 0 或 1 - 它的
acquire()和release()是无锁(lock-free)实现,在支持的平台上(如 x86-64 + GCC/Clang + libstdc++/libc++),底层用的是原子操作而非 futex 或 mutex 回退
怎么写一个真正低延迟的二元同步原语
关键不是名字,而是确保它不触发内核态切换、不分配堆内存、不隐式加锁。用 std::counting_semaphore 是目前最接近标准答案的做法,但要注意初始化和使用姿势:
- 必须在静态存储期或线程本地初始化,避免构造时做运行时检查(某些 libc++ 实现会在 debug 模式下检查
N > 0,虽不慢但也非零开销) - 不要在 hot path 上反复
release()后立刻acquire()——这会引发不必要的原子 RMW 操作;如果只是通知一次状态变化,用try_acquire()避免阻塞更合适 - 若需“等待直到某条件成立”,别直接轮询
try_acquire(),应搭配std::this_thread::yield()或短时std::this_thread::sleep_for(1ns)(实际被截断为最小调度粒度)
示例(生产者-消费者风格的一次性通知):
立即学习“C++免费学习笔记(深入)”;
std::counting_semaphore<1> sem{0};
// 线程 A(生产者)
do_work();
sem.release(); // 原子 + 内存序 relaxed(默认)
// 线程 B(消费者)
sem.acquire(); // 阻塞直到 release 被调用;底层是 futex_wait(Linux)或 WaitOnAddress(Windows)
do_something_after_signal();
为什么不用 std::mutex + std::condition_variable?
因为它们不是轻量级的:
-
std::mutex在争用时大概率触发内核态 futex_wait,延迟在微秒级甚至更高;而std::counting_semaphore在无争用时是纯用户态原子操作(纳秒级) -
std::condition_variable::wait()必须搭配std::mutex,引入额外的锁开销和虚假唤醒处理逻辑 -
std::counting_semaphore的acquire()不会虚假唤醒,语义更干净 - 但注意:一旦
acquire()阻塞,它最终仍要进内核等待(否则无法被唤醒),所以“零延迟”只存在于无争用场景;真正的低延迟靠的是减少争用路径上的开销,不是消灭阻塞本身
容易被忽略的兼容性与陷阱
看似简单,踩坑点很隐蔽:
- MSVC 19.3x 默认不启用 C++20
<semaphore></semaphore>,需显式开启/std:c++20且确认 SDK 版本 ≥ 10.0.20348.0(Win11 SDK);否则链接失败或 fallback 到模拟实现 - libstdc++(GCC)从 12.1 开始支持
std::counting_semaphore,但早期版本(如 GCC 11)只有 experimental 版本,头文件是<experimental></experimental>,且不保证 lock-free - Clang + libc++ 需要 ≥ 15.0,并启用
-stdlib=libc++;否则可能静默降级为 mutex-based 模拟 -
std::counting_semaphore不可拷贝、不可移动,也不能作为函数参数值传递——常见错误是写成foo(std::counting_semaphore s),必须传引用或指针
真正影响低延迟的,往往不是 acquire/release 本身,而是你没意识到的编译器支持差异和运行时 fallback 行为。










