[*] 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 AuInitOnce = AuThreading::InitOnce;
using AuInitOnceSmall = __audetail::InitOnceABI;
template<typename T, bool bIsStatic = false>
using AuTLSVariable = AuThreads::TLSVariable<T, bIsStatic>;

View File

@ -7,41 +7,70 @@
***/
#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
{
struct InitOnce final :
private IWaitable
private IWaitable,
private __audetail::InitOnceABI
{
inline bool IsUninitialized()
{
return (AuAtomicLoad(&this->uToken_) & 1) == 0;
return InitOnceABI::IsUninitialized();
}
inline bool IsReady()
{
return (AuAtomicLoad(&this->uToken_) & 2) != 0;
return InitOnceABI::IsReady();
}
inline bool TrySet()
{
if (AuAtomicTestAndSet(&this->uToken_, 0) == 0)
{
this->Finish();
return true;
}
else
{
return false;
}
return InitOnceABI::TrySet();
}
template <typename Callable>
bool TryInit(const Callable &callback)
{
if (AuAtomicTestAndSet(&this->uToken_, 0) == 0)
if (this->_TryAcquire())
{
callback();
this->Finish();
this->_Finish();
return true;
}
else
@ -63,12 +92,12 @@ namespace Aurora::Threading
return;
}
this->Lock();
this->Wait();
}
inline void Wait()
{
this->Lock();
InitOnceABI::Wait();
}
inline IWaitable *ToBarrier()
@ -77,39 +106,23 @@ namespace Aurora::Threading
}
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;
}
inline bool HasLockImplementation()
inline bool HasLockImplementation() override
{
return true;
}
inline void Lock()
inline void Lock() override
{
this->LockAbsNS(0);
}
inline bool LockNS(AuUInt64 qwTimeoutInNs)
inline bool LockNS(AuUInt64 qwTimeoutInNs) override
{
if (this->IsReady())
{
@ -119,39 +132,133 @@ namespace Aurora::Threading
return IWaitable::LockNS(qwTimeoutInNs);
}
inline bool LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/)
inline bool LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/) override
{
auto uCurrent = AuAtomicLoad(&this->uToken_);
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;
return this->_LockAbsNS(qwAbsTimeoutInNs);
}
inline bool TryLock()
inline bool TryLock() override
{
return this->IsReady();
}
inline void Unlock()
inline void Unlock() override
{
}
private:
AuAUInt32 uToken_ {};
AuAUInt32 uSleepers_ {};
friend struct InitOnceLocker;
};
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;
}
}