You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

这段Reentrant Lock代码是否易出现陈旧数据错误并允许多线程同时获锁?

无锁可重入锁ReentrantLock32的线程安全疑问

我在学习无锁并发中的可重入锁时,接触到了如下实现代码:

class ReentrantLock32
 {
  std::atomic<std::size_t> m_atomic;
  std::int32_t m_refCount;

public:
  ReentrantLock32() : m_atomic(0), m_refCount(0) {}

  void Acquire()
   {
    std::hash<std::thread::id> hasher;
    std::size_t tid = hasher(std::this_thread::get_id());

    if (m_atomic.load(std::memory_order_relaxed) != tid)
     {
       std::size_t unlockValue = 0;
       while (!m_atomic.compare_exchange_weak(
        unlockValue,
        tid,
        std::memory_order_relaxed,
        std::memory_order_relaxed))
       {
        unlockValue = 0;
        PAUSE();
       }
      }
      ++m_refCount;
      std::atomic_thread_fence(std::memory_order_acquire);
     }

  void Release() {
   std::atomic_thread_fence(std::memory_order_release);
   std::hash<std::thread::id> hasher;
   std::size_t tid = hasher(std::this_thread::get_id());
   std::size_t actual = m_atomic.load(std::memory_order_relaxed);
   assert(actual == tid);

   --m_refCount;
   if (m_refCount == 0)
    {
     m_atomic.store(0,std::memory_order_relaxed);
    }
 }
//...
}

我认为该代码在高线程竞争场景下,可能因陈旧数据错误导致多个线程同时获取锁。尤其当不同核心的线程同时调用如下CAS代码时:

!m_atomic.compare_exchange_weak(
        unlockValue,
        tid,
        std::memory_order_relaxed,
        std::memory_order_relaxed)

想请教:CPU缓存一致性协议是否可能未及时失效L1-cache,使得两个线程都成功获取锁?


核心结论:不会出现两个线程同时通过CAS获取锁的情况

CPU的缓存一致性协议(比如常见的MESI协议)会严格保证原子操作的全局可见性和互斥性。compare_exchange_weak是硬件级原子指令,执行时会先读取原子变量的当前值,对比后如果符合预期就写入新值,整个过程不可中断。即使不同核心的L1缓存里有该变量的副本,CAS操作触发的缓存一致性流程会立刻让其他核心的缓存副本失效,绝对不会出现两个线程同时成功执行CAS的情况。

但这段代码确实存在严重的线程安全问题

  • m_refCount不是原子变量:多个线程同时调用Acquire时,++m_refCount是普通的非原子操作,会出现竞态条件,导致引用计数错乱,进而可能出现锁提前释放或者重复释放的问题。
  • 线程ID哈希冲突风险:std::hash<std::thread::id>的哈希值可能存在碰撞,不同线程的ID哈希后得到相同的tid,会导致一个线程错误地认为自己已经持有锁,直接递增引用计数,破坏锁的互斥性。
  • 内存栅栏位置错误:
    • Acquire中的std::memory_order_acquire栅栏应该放在锁获取成功之后、递增引用计数之前,否则无法保证临界区的内存可见性;
    • Release中的std::memory_order_release栅栏应该放在递减引用计数之后、释放锁之前,否则无法保证临界区的修改对其他线程可见。

内容的提问来源于stack exchange,提问作者Josh Hardman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 14:05:16