std::atomic_flag 是最轻量的无锁原子布尔类型,专为实现自旋锁设计,仅支持 test_and_set() 和 clear(),强制 lock-free 且初始化必须用 ATOMIC_FLAG_INIT 或 {}。
std::atomic_flag 是 C++ 中最轻量的原子布尔类型,它不提供 load() 或 store(),只支持 test_and_set() 和 clear() —— 这恰恰让它成为实现**无锁自旋锁**的理想起点。
看似更直观,但 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()
核心逻辑就两行:循环调用 test_and_set() 直到返回 false(说明之前是未设置状态,抢锁成功);退出临界区时调用 clear()。
struct spin_lock {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
void lock() {
while (flag.test_and_set(std::me
mory_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 前的读写不被重排到锁释放后std::this_thread::yield() 或 x86 的 _mm_pause()
看似简单,但几个细节一错就导致死锁或数据竞争:
std::atomic_flag flag; 是未定义行为 —— 必须用 = ATOMIC_FLAG_INIT 或 {}(C++17)flag.clear(std::memory_order_relaxed) 会破坏同步语义,其他线程可能看不到临界区内的修改clear() 而未配对 lock(),会导致锁状态混乱spin_lock 放在栈上并跨线程传递(比如 move 到 lambda)—— 它不可移动、不可拷贝,运行时报错或静默 UB自旋锁只适合「临界区极短 + 争用极少」的场景,比如保护一个计数器更新、或无锁结构中的某个标志位。
std::mutex 写业务逻辑 —— 几乎总是错的真正需要它的场合,往往已经处于无锁编程的深水区,此时你大概率已在手写 std::atomic 状态机,而不是靠 std::atomic_flag 打天下。