本文使用内存屏障来 VolatileRead、VolatileWrite 保证读写重入自旋锁的当前线程ID(tid),如果不愿意采用内存屏障的方式来处理,人们可以选择使用原子变量读写来代替。
C++ 置R/W内存屏障:
cpp
static inline void MemoryBarrier() noexcept
{
std::atomic_thread_fence(std::memory_order_seq_cst);
}
头文件:
cpp
#pragma once
#include <ppp/stdafx.h>
namespace ppp
{
namespace threading
{
class SpinLock final
{
public:
explicit SpinLock() noexcept;
SpinLock(const SpinLock&) = delete;
SpinLock(SpinLock&&) = delete;
~SpinLock() noexcept(false);
public:
SpinLock& operator=(const SpinLock&) = delete;
public:
bool TryEnter() noexcept;
bool TryEnter(int loop, int timeout) noexcept;
void Enter() noexcept;
void Leave();
inline bool IsLockTaken() noexcept { return _.load(); }
public:
inline void lock() noexcept { Enter(); }
inline void unlock() noexcept { Leave(); }
public:
std::atomic<bool> _;
};
class RecursiveSpinLock final
{
public:
explicit RecursiveSpinLock() noexcept;
RecursiveSpinLock(const RecursiveSpinLock&) = delete;
RecursiveSpinLock(RecursiveSpinLock&&) = delete;
~RecursiveSpinLock() = default;
public:
RecursiveSpinLock& operator=(const RecursiveSpinLock&) = delete;
public:
bool TryEnter() noexcept;
bool TryEnter(int loop, int timeout) noexcept;
void Enter() noexcept;
void Leave();
inline bool IsLockTaken() noexcept { return lockobj_.IsLockTaken(); }
public:
inline void lock() noexcept { Enter(); }
inline void unlock() noexcept { Leave(); }
public:
SpinLock lockobj_;
volatile int tid_;
std::atomic<int> reentries_;
};
}
}
源文件:
cpp
#include <ppp/threading/SpinLock.h>
#include <ppp/threading/Thread.h>
namespace ppp
{
namespace threading
{
template <class LockObject>
static constexpr bool Lock_TryEnter(
LockObject& lock,
int loop,
int timeout) noexcept
{
bool lockTaken = false;
if (loop > -1)
{
uint64_t last = GetTickCount();
for (int i = 0; i < loop; i++)
{
lockTaken = lock.TryEnter();
if (lockTaken)
{
break;
}
if (timeout > -1)
{
uint64_t now = GetTickCount();
int64_t diff = now - last;
if (diff >= timeout)
{
break;
}
}
}
}
else
{
uint64_t last = GetTickCount();
for (;;)
{
lockTaken = lock.TryEnter();
if (lockTaken)
{
break;
}
if (timeout > -1)
{
uint64_t now = GetTickCount();
int64_t diff = now - last;
if (diff >= timeout)
{
break;
}
}
}
}
return lockTaken;
}
template <class LockObject, class LockInternalObject, typename... TryEnterArguments>
static constexpr bool RecursiveLock_TryEnter(LockObject& lock,
LockInternalObject& lock_internal,
volatile int* tid,
std::atomic<int>& reentries,
TryEnterArguments&&... arguments)
{
int n = ++reentries;
assert(n > 0);
int current_tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
if (n == 1)
{
bool lockTaken = lock_internal.TryEnter(std::forward<TryEnterArguments>(arguments)...);
if (!lockTaken)
{
reentries--;
return false;
}
Thread::MemoryBarrier();
*tid = current_tid;
Thread::MemoryBarrier();
}
else
{
Thread::MemoryBarrier();
int lockTaken_tid = *tid;
Thread::MemoryBarrier();
if (lockTaken_tid == current_tid)
{
lock.Leave();
return false;
}
}
return true;
}
SpinLock::SpinLock() noexcept
: _(false)
{
}
SpinLock::~SpinLock() noexcept(false)
{
bool lockTaken = IsLockTaken();
if (lockTaken)
{
throw std::runtime_error("fail to release the atomic lock.");
}
}
void SpinLock::Enter() noexcept
{
for (;;)
{
bool lockTaken = TryEnter();
if (lockTaken)
{
break;
}
}
}
bool SpinLock::TryEnter(int loop, int timeout) noexcept
{
return Lock_TryEnter(*this, loop, timeout);
}
bool SpinLock::TryEnter() noexcept
{
bool expected = false;
return _.compare_exchange_strong(expected, true, std::memory_order_acquire);
}
void SpinLock::Leave()
{
bool expected = true;
if (!_.compare_exchange_strong(expected, false, std::memory_order_release))
{
throw std::runtime_error("failed to acquire the atomic lock.");
}
}
RecursiveSpinLock::RecursiveSpinLock() noexcept
: lockobj_()
, tid_(0)
, reentries_(0)
{
}
bool RecursiveSpinLock::TryEnter() noexcept
{
return RecursiveLock_TryEnter(*this, lockobj_, &tid_, reentries_);
}
bool RecursiveSpinLock::TryEnter(int loop, int timeout) noexcept
{
return RecursiveLock_TryEnter(*this, lockobj_, &tid_, reentries_, loop, timeout);
}
void RecursiveSpinLock::Enter() noexcept
{
for (;;)
{
bool lockTaken = TryEnter();
if (lockTaken)
{
break;
}
}
}
void RecursiveSpinLock::Leave()
{
int n = --reentries_;
assert(n >= 0);
if (n == 0)
{
lockobj_.Leave();
}
}
}
}