为何C++的compare_exchange不允许失败一致性强于成功一致性?
C++原子操作内存模型限制相关问题解答
背景与代码实现
我实现了一种类锁结构,可让线程阻塞直至排他锁释放。该阻塞机制对两个派生类有用:
- 传统读写锁在尝试获取共享锁时等待排他锁释放;
- 类RCU读锁允许协程读取共享数据直至挂起。
代码实现如下:
struct LockBase { static constexpr int kUnlocked = 0; static constexpr int kLocked = 1; static constexpr int kLockedWantNotify = 2; std::atomic_int status_{kUnlocked}; // ... void unlock() noexcept { if (status_.exchange(kUnlocked) == kLockedWantNotify) status_.notify_all(); } void block_until_unlocked() noexcept { for (;;) { int prev = status_.load(std::memory_order_acquire); if (prev == kLocked) status_.compare_exchange_weak( prev, kLockedWantNotify, std::memory_order_relaxed, // success ordering std::memory_order_acquire); // failure ordering if (prev == kUnlocked) return; status_.wait(kLockedWantNotify); } } };
设计思路:若compare_exchange失败且原值变为unlocked,则需memory_order_acquire语义同步解锁线程;若成功则进入futex休眠,memory_order_relaxed语义即可。
编译报错信息
部分Clang版本编译时出现如下错误:
/usr/include/c++/14/bits/atomic_base.h:536:43: error: failure memory model ‘memory_order_acquire’ cannot be stronger than success memory model ‘memory_order_relaxed’ for ‘bool __atomic_compare_exchange_4(volatile void*, void*, unsigned int, bool, int, int)’ [-Werror=invalid-memory-model] 536 | return __atomic_compare_exchange_n(&_M_i, &__i1, __i2, 1, | ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~ 537 | int(__m1), int(__m2)); | ~~~~~~~~~~~~~~~~~~~~~ /usr/include/c++/14/bits/atomic_base.h:536:43: note: valid models are 'memory_order_relaxed'
问题解答
1. Clang的报错是否符合C++标准?
符合。C++标准明确规定,原子比较交换操作(compare_exchange_weak/compare_exchange_strong)的失败内存顺序不能强于成功内存顺序。比如不能出现成功用memory_order_relaxed而失败用memory_order_acquire的组合,这属于违反标准的用法,编译器有权报错。
2. C++为何设置此限制?
这个限制基于内存模型的一致性和硬件实现的现实情况:
- 语义一致性:成功的CAS操作会修改原子变量,失败的CAS操作仅读取当前值。如果失败的内存顺序强于成功的,会导致逻辑矛盾——修改操作的内存同步要求更低,反而读取操作要求更高,不符合原子操作的设计逻辑。
- 硬件实现约束:多数硬件平台上,CAS操作的成功和失败路径共享同一套内存屏障逻辑。允许失败顺序强于成功顺序的话,编译器需要为失败路径额外插入更强的内存屏障,会带来不必要的性能开销,甚至部分硬件无法高效实现这种不对称的内存语义。
3. 编译器为何不能自动提升失败一致性以兼容该场景?
编译器不会自动提升成功的内存顺序来匹配失败的,原因如下:
- 标准要求明确:标准不允许这种不对称的内存顺序组合,编译器必须严格遵循标准做错误检查,而非自行修改用户指定的内存顺序。
- 用户意图优先:用户指定
memory_order_relaxed作为成功顺序,通常是为了追求性能,明确不需要额外的内存同步。自动提升为acquire会违背用户的性能优化意图,引入不必要的内存屏障开销。 - 避免隐蔽行为:自动修改内存顺序会让代码行为变得不透明,用户无法准确预测内存同步的实际效果,增加调试和维护难度。
内容的提问来源于stack exchange,提问作者user3188445
相关产品推荐
相关产品推荐

