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.