std::atomic_flag 是无锁原子布尔标记,仅支持 test_and_set() 和 clear(),需手动配 memory_order_acquire/release 实现自旋锁;不可直接当锁用,误用会导致死循环或未定义行为。

std::atomic_flag 是什么,为什么不能直接当锁用
它不是锁,只是个原子布尔标记,连 load() 和 store() 都不支持(C++20 前只能用 test_and_set() 和 clear())。想靠它实现自旋锁,必须手动轮询 + 内存序控制,否则会读到陈旧值或触发未定义行为。
常见错误现象:while (flag.test_and_set()) {} 死循环卡住 —— 不是因为没抢到,而是因为编译器优化掉了重复读取,或者 CPU 没看到其他线程的写入。
- 必须用
memory_order_acquire保证后续操作不会被重排到加锁前 - 必须用
memory_order_release保证临界区操作全部完成后再清标志 -
test_and_set()默认是memory_order_seq_cst,性能高但没必要;显式指定更安全也更清晰
怎么写一个最小可用的自旋锁
核心就三步:尝试获取、忙等、释放。别封装成类也行,但得把内存序写对,不然多核下大概率出错。
std::atomic_flag lock = ATOMIC_FLAG_INIT;
// 加锁
while (lock.test_and_set(std::memory_order_acquire)) {
// 可选:__builtin_ia32_pause() 或 std::this_thread::yield()
}
// 临界区
do_something();
// 解锁
lock.clear(std::memory_order_release);
-
ATOMIC_FLAG_INIT是唯一合法初始化方式,{}或= {}在 C++17 后可能失效 - 忙等时加
pause指令(x86)能降低功耗和总线争用,std::this_thread::yield()开销更大,慎用 - 别在临界区内调用可能阻塞的函数(如
malloc、std::cout),自旋锁只适合极短操作
std::atomic_flag 和 std::atomic 有什么区别
前者保证无锁(lock-free),后者不一定。用 std::atomic 写自旋锁看似简单,但 x86 下可能生成 lock xchgb,ARM 下甚至退化为互斥量模拟 —— 这就不是无锁了。
立即学习“C++免费学习笔记(深入)”;
- 查是否真无锁:
lock.is_lock_free()必须返回true,不是“理论上可以” -
std::atomic的exchange()虽然也能做 test-and-set,但语义不如test_and_set()明确,且无法强制无锁 - 别图省事用
std::atomic模拟,哪怕只用 0/1,也破坏了原子标志的硬件级保证
哪些场景下 std::atomic_flag 自旋锁反而更差
等待时间稍长(比如 > 几百纳秒)、线程数远超 CPU 核心数、或临界区有系统调用时,它会让 CPU 白跑,吞光调度时间片,拖慢整个程序。
- Linux 下可观察到
top里 CPU 占用 100%,但实际吞吐没提升,甚至下降 - 调试时若卡在
test_and_set()循环里,别急着断点 —— 先确认是不是持有锁的线程崩溃或死锁了 - 嵌入式或实时系统中,如果中断上下文要参与同步,
std::atomic_flag仍可用,但得确保clear()不被中断打断(通常默认满足)
真正难的是判断“多短才算短”——这得看具体负载、缓存一致性协议、以及你愿不愿意为 20ns 的开销多写 10 行代码。











