std::atomic_flag 是最轻量的无锁原子布尔类型,专为实现自旋锁设计,仅支持 test_and_set() 和 clear(),强制 lock-free 且初始化必须用 ATOMIC_FLAG_INIT 或 {}。

std::atomic_flag 是 C++ 中最轻量的原子布尔类型,它不提供 load() 或 store(),只支持 test_and_set() 和 clear() —— 这恰恰让它成为实现**无锁自旋锁**的理想起点。
为什么不用 std::atomic 做自旋锁?
看似更直观,但 std::atomic 的 exchange(true, std::memory_order_acquire) 在某些平台(如 ARM)可能生成较重的指令;而 std::atomic_flag 被标准强制要求是“无锁的”(lock-free),且 test_and_set() 默认使用 std::memory_order_seq_cst,语义更贴近互斥锁的 acquire/release 行为。
-
std::atomic_flag初始化必须用ATOMIC_FLAG_INIT(C++17 起可直接用{}值初始化) - 它不支持拷贝、赋值,天然防误用
- 没有
operator bool(),必须显式调用test_and_set()或clear()
最简自旋锁:spin_lock 类封装
核心逻辑就两行:循环调用 test_and_set() 直到返回 false(说明之前是未设置状态,抢锁成功);退出临界区时调用 clear()。
struct spin_lock {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
// 可选:__builtin_ia32_pause() 或 std::this_thread::yield()
}
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
- 用
std::memory_order_acquire保证 lock 后的读写不被重排到锁获取前 - 用
std::memory_order_release保证 unlock 前的读写不被重排到锁释放后 - 若长期争用,空转消耗 CPU,建议在循环体内加
std::this_thread::yield()或 x86 的_mm_pause()
常见误用与陷阱
看似简单,但几个细节一错就导致死锁或数据竞争:
立即学习“C++免费学习笔记(深入)”;
- 忘记初始化:写成
std::atomic_flag flag;是未定义行为 —— 必须用= ATOMIC_FLAG_INIT或{}(C++17) - unlock 时用了错误的 memory order:
flag.clear(std::memory_order_relaxed)会破坏同步语义,其他线程可能看不到临界区内的修改 - 构造函数里没初始化 flag,或多次调用
clear()而未配对lock(),会导致锁状态混乱 - 把
spin_lock放在栈上并跨线程传递(比如 move 到 lambda)—— 它不可移动、不可拷贝,运行时报错或静默 UB
和 std::mutex 对比:什么时候该用它?
自旋锁只适合「临界区极短 + 争用极少」的场景,比如保护一个计数器更新、或无锁结构中的某个标志位。
- 优势:无系统调用开销,上下文切换少,在低延迟关键路径中更快
- 劣势:持有锁时持续占用 CPU,高争用下吞吐反而更差;无法被操作系统调度器挂起,不能用于可能阻塞的操作(如 IO、sleep)
- 不要试图用它替代
std::mutex写业务逻辑 —— 几乎总是错的
真正需要它的场合,往往已经处于无锁编程的深水区,此时你大概率已在手写 std::atomic 状态机,而不是靠 std::atomic_flag 打天下。











