这段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); } } //... }
核心疑问解答
1. 内存栅栏的重排阻止能力
- release栅栏(
memory_order_release):仅能阻止栅栏之前的非原子操作、relaxed原子操作被重排到栅栏之后;无法约束栅栏之后的操作重排到栅栏之前。 - acquire栅栏(
memory_order_acquire):仅能阻止栅栏之后的非原子操作、relaxed原子操作被重排到栅栏之前;无法约束栅栏之前的操作重排到栅栏之后。
2. 同一线程内的操作重排风险
你担心的场景确实存在严重风险:
当线程执行Release()且m_refCount减至0时,代码会执行m_atomic.store(0, relaxed)。但由于该store是relaxed语义,且release栅栏位于函数开头(无法约束后续的store操作),结合编译器或CPU的重排优化,可能出现:if (m_refCount == 0)判断完成后,同一线程立即调用Acquire()——此时Acquire()中m_atomic.load(relaxed)可能仍读到旧值(当前线程的tid),于是直接跳过CAS逻辑、递增m_refCount;而之前Release()中的store(0)操作被延迟执行,最终将m_atomic设为0。
这会导致锁的互斥性被破坏:当前线程仍持有锁(m_refCount > 0),但m_atomic已被置为0,其他线程可以通过CAS获取锁,引发并发安全问题。
3. 陈旧数据导致的死锁问题
代码中Acquire()的m_atomic.load(relaxed)存在陈旧数据风险:
若当前线程已经持有锁(m_atomic的值是当前线程tid),但由于relaxed加载的可见性问题,load操作读到了旧值0,代码会进入CAS循环。而循环中每次都将unlockValue重置为0,用0去和实际值为tid的m_atomic做CAS对比,必然持续失败,最终导致线程死锁。
内容的提问来源于stack exchange,提问作者Josh Hardman
相关产品推荐
相关产品推荐

