std::atomic_flag是C++标准中唯一保证无锁的原子类型,专为自旋锁设计;它仅提供test_and_set()和clear()两个操作,必须用ATOMIC_FLAG_INIT或默认构造初始化,并配合memory_order_acquire/release内存序使用,避免退化为互斥量或重排问题。

std::atomic_flag 是最轻量的自旋锁基础
标准库里没有 std::spinlock,但 std::atomic_flag 是专为自旋锁设计的无锁原子类型——它保证是 lock-free 的,且只有 test_and_set() 和 clear() 两个操作,天然适配“忙等+置位”逻辑。别用 std::atomic<bool></bool> 模拟,它在某些平台可能不是 lock-free,会悄悄退化成 mutex 调用。
初始化必须用 ATOMIC_FLAG_INIT(C++17 起已弃用,改用默认构造):
std::atomic_flag flag = ATOMIC_FLAG_INIT; // C++11/14
// 或 C++17+
std::atomic_flag flag{};
正确实现 try_lock / lock / unlock 的三步闭环
自旋锁的核心是“测试-设置-失败则重试”,不能漏掉内存序控制,否则编译器或 CPU 乱序会导致可见性问题。关键点:
-
test_and_set()必须用std::memory_order_acquire:获取锁时确保后续读写不被重排到锁之前 -
clear()必须用std::memory_order_release:释放锁时确保之前的读写不被重排到锁之后 - 忙等循环里加
std::this_thread::yield(),避免空转榨干 CPU 资源(尤其在单核或高争用场景)
示例:
立即学习“C++免费学习笔记(深入)”;
class spinlock {
std::atomic_flag flag{};
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
std::this_thread::yield();
}
}
bool try_lock() {
return !flag.test_and_set(std::memory_order_acquire);
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
为什么不能直接用 atomic::exchange()?
常见误区是用 std::atomic<bool></bool> + exchange(true) 实现锁,但问题不少:
-
std::atomic<bool></bool>不保证 lock-free;exchange()在非 lock-free 实现下可能内部用 mutex,彻底失去自旋意义 - 缺少显式内存序控制,默认
memory_order_seq_cst开销大,且语义过强——自旋锁只需 acquire/release 即可 - 初始化风险:
std::atomic<bool> flag{false}</bool>在某些旧编译器上可能未正确 zero-initialize,而std::atomic_flag默认构造即清零
实际使用中容易卡死的三个坑
自旋锁不是万能银弹,这些情况会直接导致死锁或性能雪崩:
- 锁内调用阻塞操作(如
std::cout、malloc、系统调用)——自旋锁必须短平快,否则其他线程永远抢不到 - 递归持有:
lock()再次调用会无限自旋,它不记录 owner 线程,也没重入计数 - 中断上下文误用:在 Linux kernel module 或信号处理函数里用用户态自旋锁,可能因禁用抢占而卡死
真正需要自旋锁的场景极少,通常是无锁数据结构内部极短临界区,或者实时系统中禁止睡眠的上下文。多数业务代码,老老实实用 std::mutex 更安全。


















