compare_exchange_weak适合做自旋锁因其失败开销更低,匹配“大概率快速成功、偶尔重试”场景,但需循环处理伪失败;unlock必须用store而非CAS以保证幂等性与可靠性。

compare_exchange_weak为什么适合做自旋锁
因为它的失败开销比 compare_exchange_strong 更低:在某些平台(比如 x86)上,它允许底层用 cmpxchg 指令的弱形式,失败时不必重试整个指令序列,编译器可生成更紧凑的汇编;而锁的典型场景是“大概率快速成功、偶尔重试”,compare_exchange_weak 正好匹配这种模式。
但要注意:它可能伪失败(spurious failure)——即使当前值等于期待值,也可能返回 false。所以必须用循环包裹,不能只试一次。
- 必须配合 do-while 或 while 循环使用,否则无法保证原子性获取
- 不能用于需要严格一次尝试的逻辑(比如某些状态机跃迁),此时应选
compare_exchange_strong - 在 ARM 等弱内存序平台上,伪失败概率更高,循环次数可能略增,但不影响正确性
手写 spinlock 的最小可行实现
一个可用的非阻塞自旋锁只需要一个 std::atomic<bool></bool> 成员和两个成员函数:
struct spinlock {
std::atomic<bool> locked_{false};
void lock() {
bool expected = false;
while (!locked_.compare_exchange_weak(expected, true, std::memory_order_acquire)) {
expected = false; // 重置 expected,应对伪失败
}
}
void unlock() {
locked_.store(false, std::memory_order_release);
}
};
关键点:
立即学习“C++免费学习笔记(深入)”;
-
compare_exchange_weak第三个参数是 success memory order,这里用std::memory_order_acquire,确保后续临界区读操作不会被重排到锁获取之前 - 失败后必须重置
expected,因为伪失败时expected可能被修改为当前实际值(即true),下次循环若不重置就会永远失败 -
unlock()用std::memory_order_release,与 acquire 配对,构成 acquire-release 同步,保证临界区写对其他线程可见
为什么不能直接用 compare_exchange_weak(true, false)
常见误解是想“把 true 改成 false 来解锁”,但这是错的:compare_exchange_weak 的语义是「如果当前值等于 expected,就把它改成 desired」。锁的持有态是 true,释放时你并不知道此刻是否仍为 true(比如已被其他线程抢先释放),更关键的是——这不是原子“设置为 false”,而是“仅当仍是 true 才设为 false”,这会破坏 unlock 的幂等性和可靠性。
-
unlock()必须无条件、单向地清除状态,store()是唯一安全选择 - 试图用 CAS 实现 unlock,会导致锁丢失、死锁或 ABA 相关竞态
- 标准库的
std::mutex解锁也不依赖 CAS,原理相同
实际使用时容易漏掉的细节
这个锁看着简单,但上线后常因以下原因出问题:
- 没加
std::memory_order_acquire和std::memory_order_release—— 在弱序架构(ARM/PowerPC)上可能导致临界区代码被乱序执行,产生未定义行为 - 忘记在
lock()循环里重置expected,尤其在多核高争用下,伪失败频发,导致无限循环 - 在 RAII 封装中没处理异常安全:若构造函数抛异常,
unlock()不会被调用,但此时锁其实没被持有没有问题;真正危险的是析构时调用unlock()而锁未被持有(比如 lock 失败过),应加 guard 判断或用std::atomic_flag替代 - 自旋锁不适合长临界区——CPU 白耗,建议临界区控制在百纳秒级;更长的应改用
std::mutex或 futex-based 实现
最简健壮版本建议用 std::atomic_flag(它保证无锁且初始化明确),但若已用 std::atomic<bool></bool>,务必守住 memory order 和 expected 重置这两条线。


















