代码之家  ›  专栏  ›  技术社区  ›  a a

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

  •  1
  • a a  · 技术社区  · 2 年前

    为什么我们需要 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;
        }
    };
    
    

    编辑:示例摘自JasonGregory的《游戏引擎架构第三版》

    1 回复  |  直到 2 年前
        1
  •  2
  •   Quimby    2 年前

    实现递归锁定需要该计数。如果它不在那里, Release 将始终解锁,无论有多少 Acquire 在很多情况下,这并不是你所期望和想要的。

    考虑以下常见模式:

    void helper_method(){
        Acquire();
        // Work #2
    
        Release();
    }
    
    void method(){
        Acquire();
        // Work #1
    
        helper_method();
    
        // Work #3
        Release();
    }
    

    如果锁不是递归的,就必须小心。那样的话 #3 不再在锁定下调用,您现在有一个难以跟踪的bug。这只是因为 Release() 在里面 helper_method 打开锁,真诚地这样做,因为它一开始就锁定了它,而不知道它之前已经锁定了。 这也是 std::mutex std::recursive_mutex ,两次锁定前一个是UB(根据我的经验,通常会死锁)。