CodeWalk

自旋锁的C++实现与性能分析

作者:屠龙少年 · 2026-05-30 12:55

如何用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(休眠) |

性能问题

  1. 总线风暴(Bus Storming):高冲突时大量CAS操作消耗内存总线带宽。解决:TATAS(Ticket Spinlock)或带backoff的指数退避
  2. 优先级反转:低优先级持有锁时被高优先级等待者抢占,高优先级自旋浪费CPU——低优先级无法执行释放锁。解决:非抢占式内核或优先级继承
  3. 缓存行乒乓(Cache Line Ping-Pong):多个核心争抢同一缓存行,导致缓存一致性协议大量通信
  4. 不公平:某些核心可能饿死。解决:Ticket Lock(排队自旋锁)

最佳实践:只在经过验证的短临界区使用自旋锁;通用场景用std::mutex;C++20有std::atomic::wait可实现更高效的等待。