For Prodigy Engine to support multi-threaded workloads, I needed to either use conditional statements or semaphores. This page describes the implementation of semaphores which was used in Prodigy Engine.
Below is the code implementation of Semaphores used in Prodigy Engine:
class Semaphore
{
HANDLE m_semaphore;
public:
Semaphore();
explicit Semaphore(uint initialCount, uint maxCount);
~Semaphore();
void Create(uint initialCount, uint maxCount);
void Destroy();
void Acquire();
bool TryAcquire();
void Release(uint count = 1);
// to make this work like a normal scope lock;
inline void Lock() { Acquire(); }
inline bool TryLock() { return TryAcquire(); };
inline void Unlock() { Release(1); }
};
The semaphore uses windows calls for threads to Acquire, Release and Destroy objects. The function calls incorporated for the same have been highlighted below:
//------------------------------------------------------------------------------------------------------------------------------
Semaphore::Semaphore(uint initialCount, uint maxCount)
{
Create(initialCount, maxCount);
}
//------------------------------------------------------------------------------------------------------------------------------
Semaphore::Semaphore()
{
//Does nothing, user must call Create before use
}
//------------------------------------------------------------------------------------------------------------------------------
Semaphore::~Semaphore()
{
Destroy();
}
void Semaphore::Create(uint initialCount, uint maxCount)
{
// Creating/Initializing
m_semaphore = ::CreateSemaphore(nullptr, // security attributes - ignore
initialCount, // count this starts at
maxCount, // max count this semaphore can reach
nullptr); // name, if used across processes
}
void Semaphore::Destroy()
{
if (m_semaphore != nullptr) {
::CloseHandle(m_semaphore);
m_semaphore = nullptr;
}
}
//------------------------------------------------------------------------------------------------------------------------------
// Acquire a Seamphore
// NOTE: this will block until the semaphore becomes invalid (destroyed) or succeeds
//------------------------------------------------------------------------------------------------------------------------------
void Semaphore::Acquire()
{
::WaitForSingleObject(m_semaphore, // object to wait on
INFINITE); // time to wait in milliseconds
}
//------------------------------------------------------------------------------------------------------------------------------
// NOTE: may or may not succeed
// if returns true, the counter was decremented
// if returns false, the counter was 0 and unable to be decremented
//------------------------------------------------------------------------------------------------------------------------------
bool Semaphore::TryAcquire()
{
DWORD result = ::WaitForSingleObject(m_semaphore, 0);
return (result == WAIT_OBJECT_0); // we successfully waited on the first object (m_semaphroe)
}
// releases teh seamphore - ie, adds to the counter up to max
void Semaphore::Release(uint count)
{
::ReleaseSemaphore(m_semaphore,
count, // how many to add to the semaphore
nullptr); // out for previous count
}
With semaphores and asynchronous data structures in place, Prodigy Engine can now support multi-threading and uses these features in it’s job system.
