为什么在这个可重入锁示例中我们需要一个引用计数?
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::mutex
和std::recursive_mutex
的原因,前者两次加锁就是UB(我的经验经常会死锁)。
为什么我们在下面的例子中需要 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::mutex
和std::recursive_mutex
的原因,前者两次加锁就是UB(我的经验经常会死锁)。