Semaphores

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.