为什么在这个可重入锁示例中我们需要一个引用计数?

why do we need a reference count in this Reentrant lock example?

为什么我们在下面的例子中需要 m_refCount?如果我们将其遗漏并删除 if 语句并将其主体留在那里会发生什么?

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 this thread doesn't already hold the lock...
        if (m_atomic.load(std::memory_order_relaxed) != tid)
        {
            // ... spin wait until we do hold it
            std::size_t unlockValue = 0;
            while (!m_atomic.compare_exchange_weak(
                unlockValue,
                tid,
                std::memory_order_relaxed, // fence below!
                std::memory_order_relaxed))
            {
                unlockValue = 0;
                PAUSE();
            }
        }
        // increment reference count so we can verify that
        // Acquire() and Release() are called in pairs
        ++m_refCount;
        // use an acquire fence to ensure all subsequent
        // reads by this thread will be valid
        std::atomic_thread_fence(std::memory_order_acquire);
    }
    void Release()
    {
        // use release semantics to ensure that all prior
        // writes have been fully committed before we unlock
        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)
        {
            // release lock, which is safe because we own it
            m_atomic.store(0, std::memory_order_relaxed);
        }
    }
    bool TryAcquire()
    {
        std::hash<std::thread::id> hasher;
        std::size_t tid = hasher(std::this_thread::get_id());
        bool acquired = false;
        if (m_atomic.load(std::memory_order_relaxed) == tid)
        {
            acquired = true;
        }
        else
        {
            std::size_t unlockValue = 0;
            acquired = m_atomic.compare_exchange_strong(
                unlockValue,
                tid,
                std::memory_order_relaxed, // fence below!
                std::memory_order_relaxed);
        }
        if (acquired)
        {
            ++m_refCount;
            std::atomic_thread_fence(
                std::memory_order_acquire);
        }
        return acquired;
    }
};

编辑:示例来自 Jason Gregory 的一本名为“游戏引擎架构第 3 版”的书

实现递归锁定需要计数。如果它不存在,无论有多少 Acquire 调用,Release 总是会解锁,这在许多情况下不是您所期望和想要的。

考虑以下常见模式:

void helper_method(){
    Acquire();
    // Work #2

    Release();
}

void method(){
    Acquire();
    // Work #1

    helper_method();

    // Work #3
    Release();
}

如果锁不是递归的,就要小心了。在那种情况下,#3 不再被锁定调用,您现在有一个难以追踪的错误。发生这种情况只是因为 helper_method 中的 Release() 解锁了锁,善意地这样做,因为它首先锁定了它,不知道它之前已经被锁定了。 这也是为什么有std::mutexstd::recursive_mutex的原因,前者两次加锁就是UB(我的经验经常会死锁)。