自旋锁的C++实现与性能分析
如何用C++ std::atomic实现一个自旋锁(Spin Lock)?自旋锁与互斥锁(std::mutex)的核心区别是什么?为什么自旋锁适合短临界区?自旋锁会带来哪些性能问题(总线风暴/优先级反转)?
回答
屠龙少年
自旋锁实现:
class SpinLock {
std::atomic<bool> flag{false};
public:
void lock() {
while (flag.exchange(true, std::memory_order_acquire)) {
// 忙等待(可加入pause指令降低功耗)
while (flag.load(std::memory_order_relaxed)) {
__builtin_ia32_pause(); // x86 pause
}
}
}
void unlock() {
flag.store(false, std::memory_order_release);
}
};
vs std::mutex: | 特性 | 自旋锁 | mutex | |------|--------|-------| | 等待方式 | 忙等待(CPU空转) | 休眠+上下文切换 | | 适合场景 | 极短临界区(纳秒级) | 较长时间等待 | | 上下文切换 | 无 | 有(高开销) | | CPU占用 | 等待时100% | 0(休眠) |
性能问题:
- 总线风暴(Bus Storming):高冲突时大量CAS操作消耗内存总线带宽。解决:TATAS(Ticket Spinlock)或带backoff的指数退避
- 优先级反转:低优先级持有锁时被高优先级等待者抢占,高优先级自旋浪费CPU——低优先级无法执行释放锁。解决:非抢占式内核或优先级继承
- 缓存行乒乓(Cache Line Ping-Pong):多个核心争抢同一缓存行,导致缓存一致性协议大量通信
- 不公平:某些核心可能饿死。解决:Ticket Lock(排队自旋锁)
最佳实践:只在经过验证的短临界区使用自旋锁;通用场景用std::mutex;C++20有std::atomic::wait可实现更高效的等待。