因为 std::atomic 不支持阻塞等待,仅提供无锁操作;实现信号量需等待值大于零,必须借助 futex 等系统原语,否则忙等或死锁。

为什么不能直接用 std::atomic 实现标准语义的信号量
因为 std::atomic 本身不提供「等待直到值大于零」的能力——它只支持无锁读写和 CAS,但阻塞等待必须依赖系统原语(如 futex 或条件变量)。硬用 while (count.load() 会空转耗 CPU,违背高性能初衷。
- 空转循环在高竞争或低信号量值时导致严重性能退化
-
std::atomic不保证唤醒顺序,无法实现 FIFO 等公平性要求 - 单纯计数器无法表达「有线程正在等待」这一状态,导致丢失唤醒(lost wakeup)
用 std::atomic + futex 实现 Linux 下真正高性能的 Semaphore
Linux 的 futex 是内核提供的轻量同步原语,用户态无竞争时完全不陷入内核;有竞争时才触发休眠/唤醒。C++ 标准库没封装它,但可直接调用 syscall。
- 核心思路:用
std::atomic<int></int>存储当前可用资源数,用futex管理等待队列 - 关键函数是
syscall(SYS_futex, &count, FUTEX_WAIT_PRIVATE, expected, nullptr)和FUTEX_WAKE_PRIVATE - 注意:必须检查
futex返回值,EAGAIN表示值已变,需重试;EINTR需重入 - 递减操作(
acquire)要先 CAS 减一,失败则进入futex wait;递增操作(release)先加一,再调futex wake
// 简化版 acquire(无超时)
void acquire() {
int v = count.load();
while (v > 0) {
if (count.compare_exchange_weak(v, v - 1)) return;
v = count.load();
}
// 落到这里说明 count == 0,需要等待
do {
syscall(SYS_futex, &count, FUTEX_WAIT_PRIVATE, 0, nullptr);
v = count.load();
} while (v <h3>Windows 下怎么替代 <code>futex</code>?别用 <code>std::condition_variable</code>
</h3><p><code>std::condition_variable</code> 性能远低于 <code>futex</code>,因为每次 wait/wake 都涉及 mutex 锁开销和内核对象调度。Windows 推荐用 <code>CreateSemaphoreEx</code> + <code>WaitForSingleObjectEx</code>,但更优解是使用 <code>Slim Reader/Writer Lock</code> 模拟,或直接用 <code>WaitOnAddress</code>(Win10 1607+)——它是 Windows 版的 futex。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2823" title="C++14"><img
src="https://img.php.cn/upload/manual/001/431/639/6ac8b33c327c4749.png" alt="C++14" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="overflowclass">C++14</a>
<p class="overflowclass">C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
-
WaitOnAddress参数是volatile void*、期望值、大小(通常为 4)、timeout;唤醒用WakeByAddressAll或WakeByAddressSingle - 必须确保等待地址对齐且生命周期覆盖整个等待过程(不能是栈变量被销毁)
- 与 Linux
futex类似,需配合std::atomic计数器使用:等待前确认值为 0,唤醒后检查是否真有资源可用
跨平台封装要注意的三个坑
跨平台信号量最容易翻车的地方不在逻辑,而在内存序、对齐和竞态窗口。
- 所有原子操作必须用
std::memory_order_acquire/std::memory_order_release,不能用relaxed——否则唤醒通知可能对等待线程不可见 -
futex和WaitOnAddress都要求等待地址按字长对齐(如 4 字节整型需 4 字节对齐),std::atomic默认满足,但若手动取地址传入,需用alignas - 最隐蔽的 bug:
release时先改计数器再唤醒,但若唤醒发生在计数器更新前,等待线程可能看到旧值并再次 sleep——必须用compare_exchange或带 barrier 的 store 配合唤醒
真正难的不是写对单次 acquire/release,而是让唤醒不丢失、不重复、不假醒。一旦涉及多个等待者或频繁争用,这些细节就会暴露。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










