Because the lack of condition variable in windows(though it is introduced since vista, it’s not supported in windows XP and 2003), it is not very easy to implement a thread-safe queue in c++. Strategies for Implementing POSIX Condition Variables on Win32. What I required is to just use CriticalSection or Mutex and Event without using semaphore and condition variable.
I also tried to find an exact implementation that just using win32 native API, but no luck. So I finished one by myself. The problem is I am not 100% sure the code is thread-safe. Who can tell me it is OK or not?
class CEventSyncQueue
{
public:
CEventSyncQueue(int nCapacity = -1);
virtual ~CEventSyncQueue();
virtual void Put(void* ptr);
virtual void* Get();
protected:
int m_nCapacity;
CPtrList m_list;
CRITICAL_SECTION m_lock;
HANDLE m_hGetEvent;
HANDLE m_hPutEvent;
};
CEventSyncQueue::CEventSyncQueue(int nCapacity)
{
m_nCapacity = nCapacity;
::InitializeCriticalSection(&m_lock);
m_hPutEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
m_hGetEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
}
CEventSyncQueue::~CEventSyncQueue()
{
m_list.RemoveAll();
::CloseHandle(m_hGetEvent);
::CloseHandle(m_hPutEvent);
::DeleteCriticalSection(&m_lock);
}
void CEventSyncQueue::Put(void* ptr)
{
::EnterCriticalSection(&m_lock);
while(m_nCapacity > 0 && m_list.GetCount() >= m_nCapacity)
{
::LeaveCriticalSection(&m_lock);
//wait
if(::WaitForSingleObject(m_hPutEvent, INFINITE) != WAIT_OBJECT_0)
{
ASSERT(FALSE);
}
::EnterCriticalSection(&m_lock);
}
if(m_nCapacity > 0)
{
ASSERT(m_list.GetCount() < m_nCapacity);
}
m_list.AddTail(ptr);
::SetEvent(m_hGetEvent); //notifyAll
::LeaveCriticalSection(&m_lock);
}
void* CEventSyncQueue::Get()
{
::EnterCriticalSection(&m_lock);
while(m_list.IsEmpty())
{
::LeaveCriticalSection(&m_lock);
//wait
if(::WaitForSingleObject(m_hGetEvent, INFINITE) != WAIT_OBJECT_0)
{
ASSERT(FALSE);
}
::EnterCriticalSection(&m_lock);
}
ASSERT(!m_list.IsEmpty());
void* ptr = m_list.RemoveHead();
::SetEvent(m_hPutEvent); //notifyAll
::LeaveCriticalSection(&m_lock);
return ptr;
}
On second thoughts, it’s hardly necessary to explicitly implement a semaphore. Instead, just think about how you would implement a semaphore using events, and approach your the problem that way. My first attempt used manual-reset events, which was inefficient but manifestly correct, and then I optimized.
Please note that I haven’t debugged (or even compiled!) either of these code fragments, but they should give you the right idea. Here’s the manual-reset version:
And here’s the more efficient, auto-reset events version: