自旋锁必须用std::atomic_flag而非std::atomic,因其是c++唯一保证无锁的原子类型,默认clear且test_and_set/clear配合acquire/release内存序确保正确性;错误使用std::atomic会导致退化为阻塞锁。

自旋锁为什么必须用 std::atomic_flag 而不是 std::atomic<bool></bool>
因为 std::atomic_flag 是 C++ 标准唯一保证无锁(lock-free)的原子类型,且默认初始化为 clear 状态;而 std::atomic<bool></bool> 在某些平台(如 ARMv7、部分旧编译器)可能退化为带锁实现,导致自旋锁实际调用了互斥量,完全失去“自旋”意义。即使它看起来更直观,也绝不能用于自旋锁核心状态位。
常见错误是写成:std::atomic<bool> locked{false}</bool>,然后用 locked.exchange(true, std::memory_order_acquire) —— 这在 clang-9 + aarch64 下很可能触发 __atomic_flag_test_and_set 的 libc 锁实现,变成阻塞式等待。
- 始终用
std::atomic_flag,并显式调用.test_and_set()和.clear() - 初始化必须用
ATOMIC_FLAG_INIT(C++17 起可省略,但显式写更安全) - 不要用
load()/store()操作 flag,只用test_and_set()和clear()
如何正确设置内存序避免重排序和可见性问题
自旋锁的关键不是“不停读”,而是“读到结果后,后续临界区代码不能被提前到加锁前,释放锁后,临界区修改不能被延后到解锁后”。这就要求严格的内存序约束。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
test_and_set(std::memory_order_acquire) 保证:一旦返回 false(即成功获取锁),之前所有 acquire 操作之后的读写不会被重排到该调用之前;clear(std::memory_order_release) 保证:临界区内所有读写不会被重排到该调用之后。
- 获取锁用
std::memory_order_acquire(不是 relaxed!否则临界区读可能看到旧值) - 释放锁用
std::memory_order_release(不是 relaxed!否则临界区写可能延迟对其他线程可见) - 绝对不要在循环里用
load(std::memory_order_relaxed)轮询——这无法保证看到其他线程的clear(),可能永远不退出循环
一个可用的最小自旋锁实现长什么样
下面是一个生产可用(非教学玩具)的轻量级自旋锁,不含异常安全、递归、调试等功能,但满足 lock-free、正确内存序、可内联等基本要求:
struct spinlock {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
void lock() noexcept {
while (flag.test_and_set(std::memory_order_acquire)) {
// 可选:添加 pause 指令提示 CPU 当前是忙等
__builtin_ia32_pause(); // x86/x64
// 其他平台可空或用 asm volatile("" ::: "memory")
}
}
void unlock() noexcept {
flag.clear(std::memory_order_release);
}
};
-
lock()必须是 while 循环,不能用 if —— 因为test_and_set()返回 true 表示抢锁失败,需重试 -
__builtin_ia32_pause()对 x86/x64 很重要:降低功耗、减少总线争用、提升多核性能;ARM 上可忽略或用asm volatile("yield") - 构造函数/析构函数不操作 flag,避免静态对象初始化顺序问题
- 所有成员函数标
noexcept,因自旋锁本身不应抛异常
什么时候不该用自旋锁
自旋锁只适合临界区极短(通常建议
- Linux 上,若临界区执行超过 1–2 微秒,调度器大概率已切走当前线程,自旋白白浪费 CPU 周期
- 在单核系统或禁用抢占的环境下,自旋锁会直接死锁(无人能执行
unlock()) - 高竞争下(>2 线程争同一锁),缓存行乒乓(cache-line bouncing)会严重拖慢性能,比 mutex 更差
- std::mutex 在多数现代实现中已是 futex 优化,短临界区开销并不比自旋锁大多少,优先用它
真正需要手写自旋锁的场合极少:比如 lock-free 数据结构内部的辅助标记、内核模块、实时线程的极短同步点。日常业务逻辑里,先 benchmark 再决定是否值得替换。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










