c++oding="utf-8" ?>
不能直接用 std::atomic 模拟信号量,因其缺乏“等待-唤醒”语义,易忙等或漏唤醒;需配合 std::atomic::wait/notify_one(C++20)或 condition_variable 实现阻塞与唤醒,且必须循环检查谓词并使用 compare_exchange_weak 保证原子性。

为什么不能直接用 std::atomic<int></int> 模拟信号量
直接对 std::atomic<int></int> 做 fetch_add 或 load/store 无法保证“等待-唤醒”语义,容易忙等或漏唤醒。比如 wait() 若只轮询 load(),会持续消耗 CPU;而单纯靠减法又无法阻塞线程。信号量核心是「资源不足时挂起,资源就绪时唤醒」,这需要系统级等待原语配合原子操作。
必须搭配 std::condition_variable 吗?
不一定。C++20 起可使用 std::atomic::wait 和 std::atomic::notify_one(基于 futex 或等价机制),避免锁和条件变量开销。但要注意:wait 只在值变化时才可能返回,且需手动检查谓词——它不替代锁,只是优化等待路径。
-
wait()必须在循环中调用,因为存在虚假唤醒 - 必须用
load(std::memory_order_acquire)读取当前值,否则可能看到陈旧值 -
notify_one()不保证立即唤醒,仅提示内核“可能有等待者”
一个安全、可重入的简易实现
以下是一个基于 C++20 std::atomic<int></int> 的二元信号量(类似 mutex)示例,支持 acquire() 和 release():
class semaphore {
std::atomic<int> count_;
public:
explicit semaphore(int initial = 1) : count_(initial) {}
<pre class="brush:php;toolbar:false;">void acquire() {
while (true) {
int expected = count_.load(std::memory_order_acquire);
if (expected > 0 && count_.compare_exchange_weak(expected, expected - 1,
std::memory_order_acq_rel,
std::memory_order_acquire)) {
return;
}
// 避免忙等:仅当值为 0 时才 wait
if (expected == 0) {
count_.wait(0, std::memory_order_acquire);
}
}
}
void release() {
int old = count_.fetch_add(1, std::memory_order_release);
if (old == 0) {
count_.notify_one();
}
}
};
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
关键点:
-
compare_exchange_weak处理并发修改,失败后重试 -
wait(0)只在计数为 0 时等待,避免无谓调用 -
notify_one()放在fetch_add后判断,确保只在从 0→1 时唤醒 - 所有原子操作的内存序必须匹配:acquire/release 成对,避免重排导致逻辑错误
Windows/Linux 下行为差异与陷阱
std::atomic::wait 在不同平台底层依赖不同:Linux 用 futex,Windows 用 WaitOnAddress。这意味着:
- Windows 上若
wait()被中断(如线程被终止),可能不会抛异常,而是直接返回 —— 你仍需检查谓词 - 某些旧版 libc++(如 macOS 12 以前)未完全实现
wait,编译会失败,需加#ifdef __cpp_lib_atomic_wait - 不能对局部变量或栈上
std::atomic调用wait,其地址必须稳定(通常要求静态/全局/堆分配)
最易被忽略的是:即使用了 wait,也不能省略循环检查和 compare_exchange_weak 的重试逻辑 —— 原子性只保操作本身,不保业务语义。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










