std::atomic::exchange 不是自旋锁的推荐选择,因其非强制 lock-free,可能退化为系统调用导致阻塞;而 std::atomic_flag::test_and_set 是标准唯一强制 lock-free 的原子操作,语义明确、性能稳定、初始化安全。

std::atomic::exchange 不能直接用来实现高性能自旋锁——它能工作,但不是最优解,且在部分平台可能退化为带锁实现,导致不可预测延迟。
为什么 std::atomic::exchange 不是自旋锁的推荐选择
表面上看,flag_.exchange(true, std::memory_order_acquire) 和 flag_.test_and_set(std::memory_order_acquire) 都返回旧值、都设新值,行为相似。但底层语义和保证完全不同:
-
std::atomic_flag::test_and_set()是 C++ 标准强制要求 lock-free 的唯一原子操作,编译器必须生成单条硬件指令(如 x86 的XCHG或 ARM 的LDXR/STXR) -
std::atomic<bool>::exchange()</bool>仅在目标平台支持时才是 lock-free;否则可能回退到 libc 内部互斥量,出现隐式阻塞、优先级反转甚至死锁 - 某些嵌入式 ABI 或旧版 libstdc++ 中,
std::atomic<bool></bool>的exchange实际调用 __atomic_exchange_1,而该函数在非 lock-free 情况下会进入 futex 等系统调用路径 - 性能上,x86 下
test_and_set是单周期指令,exchange即使 lock-free 也多一层封装开销;高争用时差异会被放大
std::atomic::exchange 在自旋锁中怎么写才“不崩溃”
如果因历史代码或类型兼容性必须用 std::atomic<bool></bool>,需严格满足以下条件才能避免未定义行为:
- 初始化必须显式:用
std::atomic<bool> flag_{false}</bool>(C++17+),不能只写std::atomic<bool> flag_</bool>(未初始化状态未定义) - 加锁循环必须用
exchange(true, std::memory_order_acquire),不能用relaxed—— 否则编译器可能把临界区外的读提前到锁内,破坏同步语义 - 解锁必须用
store(false, std::memory_order_release),不能用exchange或store(relaxed) - 必须加入自旋优化:x86 平台加
_mm_pause(),ARM 加__asm__ volatile("yield"),否则高争用下缓存一致性风暴会导致性能断崖式下降
典型可运行结构:
立即学习“C++免费学习笔记(深入)”;
class SpinLockBool {
std::atomic<bool> flag_{false};
public:
void lock() {
while (flag_.exchange(true, std::memory_order_acquire)) {
_mm_pause(); // x86 only
}
}
void unlock() {
flag_.store(false, std::memory_order_release);
}
};std::atomic::exchange 的真正适用场景:非锁类原子交换
别把它当成“自旋锁替代品”,它的设计定位是“无条件覆盖 + 获取旧值”,适合以下明确不涉及互斥逻辑的场合:
- 一次性开关置位:
bool was_running = running_flag.exchange(false);—— 只关心“现在关掉,并告诉我之前是不是开着” - 指针替换(如无锁链表头更新):
T* old_head = head_.exchange(new_node, std::memory_order_acq_rel);—— 不检查当前值,直接换 - 计数器重置:
int old_val = counter_.exchange(0, std::memory_order_relaxed);—— 适用于统计归零,无需 compare-and-swap 的重试逻辑 - 与
compare_exchange_weak对比:后者用于“先检查再改”,比如自旋锁、引用计数、栈顶 push/pop;前者纯覆盖,失败率为零,无重试开销
std::atomic_flag 才是自旋锁的事实标准
所有权威实践(包括 libstdc++ 和 libc++ 的内部锁实现)都优先选用 std::atomic_flag,原因很实在:
- 构造即安全:
std::atomic_flag flag{};(C++17)或std::atomic_flag flag = ATOMIC_FLAG_INIT;(C++11/14)确保初始为 clear 状态 - 接口极简:
test_and_set()和clear()语义清晰,没有多余重载干扰判断 - 内存序更干净:加锁用
acquire,解锁用release,无需纠结exchange的acq_rel是否冗余 - 调试友好:gdb 中
print flag_能直接看到 true/false,而std::atomic<bool></bool>可能显示为未解析的内部结构
最容易被忽略的一点:自旋锁不是“越快越好”,而是“在正确前提下尽可能快”。用错类型带来的隐式阻塞,比多几个 CPU 周期更致命——它让“自旋”变成“假自旋”,彻底失去低延迟优势。



















