[*] Improve AuInitOnce API with an 8 byte variant and a locker API

[+] AuInitOnceSmall
This commit is contained in:
Reece Wilson 2023-09-17 15:26:42 +01:00
parent d2bf01ffa3
commit ff0e32ce02
2 changed files with 166 additions and 58 deletions

View File

@ -144,6 +144,7 @@ using AuFutexSemaphore = AuThreading::Waitables::FutexSemaphoreWaitable;
using AuFutexCond = AuThreading::Waitables::FutexCondWaitable; using AuFutexCond = AuThreading::Waitables::FutexCondWaitable;
using AuInitOnce = AuThreading::InitOnce; using AuInitOnce = AuThreading::InitOnce;
using AuInitOnceSmall = __audetail::InitOnceABI;
template<typename T, bool bIsStatic = false> template<typename T, bool bIsStatic = false>
using AuTLSVariable = AuThreads::TLSVariable<T, bIsStatic>; using AuTLSVariable = AuThreads::TLSVariable<T, bIsStatic>;

View File

@ -7,41 +7,70 @@
***/ ***/
#pragma once #pragma once
namespace Aurora::Threading
{
struct InitOnceLocker;
}
namespace __audetail
{
struct InitOnceABI
{
inline bool IsUninitialized();
inline bool IsReady();
inline bool TrySet();
inline void Wait();
protected:
inline bool _TryAcquire();
inline void _Finish();
inline void _Wakeup();
inline bool _LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/);
protected:
AuAUInt32 uToken_ {};
AuAUInt32 uSleepers_ {};
friend struct Aurora::Threading::InitOnceLocker;
};
}
namespace Aurora::Threading namespace Aurora::Threading
{ {
struct InitOnce final : struct InitOnce final :
private IWaitable private IWaitable,
private __audetail::InitOnceABI
{ {
inline bool IsUninitialized() inline bool IsUninitialized()
{ {
return (AuAtomicLoad(&this->uToken_) & 1) == 0; return InitOnceABI::IsUninitialized();
} }
inline bool IsReady() inline bool IsReady()
{ {
return (AuAtomicLoad(&this->uToken_) & 2) != 0; return InitOnceABI::IsReady();
} }
inline bool TrySet() inline bool TrySet()
{ {
if (AuAtomicTestAndSet(&this->uToken_, 0) == 0) return InitOnceABI::TrySet();
{
this->Finish();
return true;
}
else
{
return false;
}
} }
template <typename Callable> template <typename Callable>
bool TryInit(const Callable &callback) bool TryInit(const Callable &callback)
{ {
if (AuAtomicTestAndSet(&this->uToken_, 0) == 0) if (this->_TryAcquire())
{ {
callback(); callback();
this->Finish(); this->_Finish();
return true; return true;
} }
else else
@ -63,12 +92,12 @@ namespace Aurora::Threading
return; return;
} }
this->Lock(); this->Wait();
} }
inline void Wait() inline void Wait()
{ {
this->Lock(); InitOnceABI::Wait();
} }
inline IWaitable *ToBarrier() inline IWaitable *ToBarrier()
@ -77,39 +106,23 @@ namespace Aurora::Threading
} }
protected: protected:
inline void Finish()
{
AuAtomicSet(&this->uToken_, 1);
this->Wakeup();
}
// barrier impl:
inline void Wakeup()
{
if (auto uSleepers = AuAtomicLoad(&this->uSleepers_))
{
WakeNOnAddress((const void *)&this->uToken_, uSleepers);
}
}
inline bool HasOSHandle(AuMach &mach) inline bool HasOSHandle(AuMach &mach) override
{ {
return false; return false;
} }
inline bool HasLockImplementation() inline bool HasLockImplementation() override
{ {
return true; return true;
} }
inline void Lock() inline void Lock() override
{ {
this->LockAbsNS(0); this->LockAbsNS(0);
} }
inline bool LockNS(AuUInt64 qwTimeoutInNs) inline bool LockNS(AuUInt64 qwTimeoutInNs) override
{ {
if (this->IsReady()) if (this->IsReady())
{ {
@ -119,39 +132,133 @@ namespace Aurora::Threading
return IWaitable::LockNS(qwTimeoutInNs); return IWaitable::LockNS(qwTimeoutInNs);
} }
inline bool LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/) inline bool LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/) override
{ {
auto uCurrent = AuAtomicLoad(&this->uToken_); return this->_LockAbsNS(qwAbsTimeoutInNs);
while ((uCurrent & 2) == 0)
{
AuAtomicAdd(&this->uSleepers_, 1u);
bool bRet = WaitOnAddressSteady((const void *)&this->uToken_, &uCurrent, sizeof(uCurrent), qwAbsTimeoutInNs);
AuAtomicSub(&this->uSleepers_, 1u);
if (!bRet)
{
return this->IsReady();
}
uCurrent = AuAtomicLoad(&this->uToken_);
}
return true;
} }
inline bool TryLock() inline bool TryLock() override
{ {
return this->IsReady(); return this->IsReady();
} }
inline void Unlock() inline void Unlock() override
{ {
} }
private: private:
AuAUInt32 uToken_ {}; friend struct InitOnceLocker;
AuAUInt32 uSleepers_ {};
}; };
struct InitOnceLocker
{
cstatic bool TryLock(InitOnce *pInitOnce)
{
return pInitOnce->_TryAcquire();
}
cstatic void Finish(InitOnce *pInitOnce)
{
pInitOnce->_Finish();
}
cstatic bool TryLock(__audetail::InitOnceABI *pInitOnce)
{
return pInitOnce->_TryAcquire();
}
cstatic void Finish(__audetail::InitOnceABI *pInitOnce)
{
pInitOnce->_Finish();
}
/**
* Example usage:
*
* static AuInitOnceSmall gSmall; // or static AuInitOnce gInitOnce;
* static_assert(sizeof(gSmall) == 8);
*
* if (AuThreading::InitOnceLocker::TryLock(&gSmall))
* {
* // My non-AuInitOnce::TryInit/Call() callback logic here
* AuThreading::InitOnceLocker::Finish(&gSmall);
* }
* else
* {
* gSmall.Wait();
* }
*/
};
}
namespace __audetail
{
bool InitOnceABI::IsUninitialized()
{
return (AuAtomicLoad(&this->uToken_) & 1) == 0;
}
bool InitOnceABI::IsReady()
{
return (AuAtomicLoad(&this->uToken_) & 2) != 0;
}
bool InitOnceABI::TrySet()
{
if (this->_TryAcquire())
{
this->_Finish();
return true;
}
else
{
return false;
}
}
void InitOnceABI::Wait()
{
this->_LockAbsNS(0);
}
bool InitOnceABI::_TryAcquire()
{
return AuAtomicTestAndSet(&this->uToken_, 0) == 0;
}
void InitOnceABI::_Finish()
{
AuAtomicSet(&this->uToken_, 1);
this->_Wakeup();
}
void InitOnceABI::_Wakeup()
{
if (auto uSleepers = AuAtomicLoad(&this->uSleepers_))
{
Aurora::Threading::WakeNOnAddress((const void *)&this->uToken_, uSleepers);
}
}
bool InitOnceABI::_LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/)
{
auto uCurrent = AuAtomicLoad(&this->uToken_);
while ((uCurrent & 2) == 0)
{
AuAtomicAdd(&this->uSleepers_, 1u);
bool bRet = Aurora::Threading::WaitOnAddressSteady((const void *)&this->uToken_, &uCurrent, sizeof(uCurrent), qwAbsTimeoutInNs);
AuAtomicSub(&this->uSleepers_, 1u);
if (!bRet)
{
return this->IsReady();
}
uCurrent = AuAtomicLoad(&this->uToken_);
}
return true;
}
} }