std::atomic_flag初始化必须用ATOMIC_FLAG_INIT(C++17前),否则未定义行为;lock()须用test_and_set(std::memory_order_acquire),unlock()须用clear(std::memory_order_release),仅适用于极短临界区。

std::atomic_flag 初始化必须用 ATOMIC_FLAG_INIT(C++17前)
不初始化或用 false 直接赋值会触发未定义行为——std::atomic_flag 是一个无锁原子类型,它不提供默认构造函数(C++20前),也不能用 = {false} 这类聚合初始化以外的方式初始化。
常见错误是写成:
std::atomic_flag flag; // 错!未初始化,UB
正确做法(C++17及之前):
std::atomic_flag flag = ATOMIC_FLAG_INIT;
C++20 起支持默认构造,但语义是“清零状态”(即 test_and_set() 第一次调用返回 false),仍建议显式初始化以保持跨标准一致性:
立即学习“C++免费学习笔记(深入)”;
- 用
ATOMIC_FLAG_INIT兼容所有标准(注意:宏在 C++20 被弃用但仍保留) - 或用
std::atomic_flag flag{};(C++20+,等价于清零) - 不要用
std::atomic_flag flag{true};—— 构造函数不接受布尔参数
自旋锁的 lock() 必须用 test_and_set(std::memory_order_acquire)
自旋逻辑本质是反复尝试将标志位从 false 改为 true,成功即获得锁。关键在于内存序:若只用 std::memory_order_relaxed,编译器和 CPU 可能重排临界区读写,导致数据竞争。
正确实现:
while (flag.test_and_set(std::memory_order_acquire)) {
// 自旋等待
}
-
test_and_set()返回旧值:false表示此前未被设置,当前线程抢锁成功 -
std::memory_order_acquire保证后续临界区读操作不会被重排到锁获取之前 - 不能用
std::memory_order_consume(太弱,且实际编译器常降级为 acquire) - 避免空循环耗尽 CPU:可加
std::this_thread::yield()或 pause 指令(如_mm_pause())缓解,但不是原子性必需
unlock() 必须用 clear(std::memory_order_release)
释放锁时需确保临界区内的写操作对其他线程可见,因此必须用 std::memory_order_release —— 它与 acquire 配对形成同步关系(synchronizes-with)。
错误写法:
flag.clear(); // 默认 memory_order_seq_cst,性能高但非必需
更优写法:
flag.clear(std::memory_order_release);
-
clear()是唯一能将 flag 置为false的操作,不可用test_and_set(false)替代(该函数只接受std::memory_order参数,不接受布尔值) - 不能漏掉参数:C++11/14 中
clear()无默认参数,不传内存序就是编译错误 - 若用
std::memory_order_seq_cst,虽安全但可能带来额外屏障开销,尤其在 ARM/POWER 架构上
std::atomic_flag 自旋锁不适合长时间持锁场景
它没有阻塞机制,线程在锁被占用时持续执行 test_and_set(),消耗 CPU 周期。一旦临界区执行时间稍长(比如 > 几百纳秒),吞吐反而下降,还可能饿死其它线程。
- 适用场景:极短临界区(如更新计数器、指针交换)、用户态无调度环境(如内核模块、实时系统)
- 不适用:含 I/O、内存分配、函数调用、或任何可能阻塞的操作
- 替代方案:真正生产环境优先考虑
std::mutex;若需无锁+公平性,可用std::atomic<int>实现 ticket lock,或用 futex(Linux)/SRWLock(Windows)等系统原语 - 调试时注意:GDB 可能无法中断自旋循环中的线程,表现为 “卡死”,实则是忙等待
最简可用版本就三行核心逻辑,但内存序选错、初始化遗漏、或误用于重负载场景,都会让锁失效或拖垮性能。


















