这段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
相关产品推荐
相关产品推荐

