再想一想,几乎没有必要显式地实现信号量。相反,只需考虑如何使用事件实现信号量,并以这种方式解决问题。我的第一次尝试使用了手动重置事件,虽然效率低下但显然是正确的,然后我进行了优化。
请注意,我没有调试(甚至编译!)这些代码片段,但它们应该给你正确的想法。这是手动重置版本:
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_queue_not_empty;
HANDLE m_queue_not_full;
};
CEventSyncQueue::CEventSyncQueue(int nCapacity)
{
m_nCapacity = nCapacity;
::InitializeCriticalSection(&m_lock);
m_queue_not_empty = ::CreateEvent(NULL, TRUE, FALSE, NULL);
m_queue_not_full = ::CreateEvent(NULL, TRUE, TRUE, NULL);
}
CEventSyncQueue::~CEventSyncQueue()
{
m_list.RemoveAll();
::CloseHandle(m_queue_not_empty);
::CloseHandle(m_queue_not_full);
::DeleteCriticalSection(&m_lock);
}
void CEventSyncQueue::Put(void* ptr)
{
bool done = false;
while (!done)
{
// If the queue is full, we must wait until it isn't.
if(::WaitForSingleObject(m_queue_not_full, INFINITE) != WAIT_OBJECT_0)
{
ASSERT(FALSE);
}
// However, we might not be the first to respond to the event,
// so we still need to check whether the queue is full and loop
// if it is.
::EnterCriticalSection(&m_lock);
if (m_nCapacity <= 0 || m_list.GetCount() < m_nCapacity)
{
m_list.AddTail(ptr);
done = true;
// The queue is definitely not empty.
SetEvent(m_queue_not_empty);
// Check whether the queue is now full.
if (m_nCapacity > 0 && m_list.GetCount() >= m_nCapacity)
{
ResetEvent(m_queue_not_full);
}
}
::LeaveCriticalSection(&m_lock);
}
}
void* CEventSyncQueue::Get()
{
void *result = nullptr;
while (result == nullptr)
{
// If the queue is empty, we must wait until it isn't.
if(::WaitForSingleObject(m_queue_not_empty, INFINITE) != WAIT_OBJECT_0)
{
ASSERT(FALSE);
}
// However, we might not be the first to respond to the event,
// so we still need to check whether the queue is empty and loop
// if it is.
::EnterCriticalSection(&m_lock);
if (!m_list.IsEmpty())
{
result = m_list.RemoveHead();
ASSERT(result != nullptr);
// The queue shouldn't be full at this point!
ASSERT(m_nCapacity <= 0 || m_list.GetCount() < m_nCapacity);
SetEvent(m_queue_not_full);
// Check whether the queue is now empty.
if (m_list.IsEmpty())
{
ResetEvent(m_queue_not_empty);
}
}
::LeaveCriticalSection(&m_lock);
}
}
这是更高效的自动重置事件版本:
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_queue_not_empty;
HANDLE m_queue_not_full;
};
CEventSyncQueue::CEventSyncQueue(int nCapacity)
{
m_nCapacity = nCapacity;
::InitializeCriticalSection(&m_lock);
m_queue_not_empty = ::CreateEvent(NULL, FALSE, FALSE, NULL);
m_queue_not_full = ::CreateEvent(NULL, FALSE, TRUE, NULL);
}
CEventSyncQueue::~CEventSyncQueue()
{
m_list.RemoveAll();
::CloseHandle(m_queue_not_empty);
::CloseHandle(m_queue_not_full);
::DeleteCriticalSection(&m_lock);
}
void CEventSyncQueue::Put(void* ptr)
{
if (m_nCapacity <= 0)
{
::EnterCriticalSection(&m_lock);
m_list.AddTail(ptr);
SetEvent(m_queue_not_empty);
::LeaveCriticalSection(&m_lock);
return;
}
bool done = false;
while (!done)
{
// If the queue is full, we must wait until it isn't.
if(::WaitForSingleObject(m_queue_not_full, INFINITE) != WAIT_OBJECT_0)
{
ASSERT(FALSE);
}
// However, under some (rare) conditions we'll get here and find
// the queue is already full again, so be prepared to loop.
::EnterCriticalSection(&m_lock);
if (m_list.GetCount() < m_nCapacity)
{
m_list.AddTail(ptr);
done = true;
SetEvent(m_queue_not_empty);
if (m_list.GetCount() < m_nCapacity)
{
SetEvent(m_queue_not_full);
}
}
::LeaveCriticalSection(&m_lock);
}
}
void* CEventSyncQueue::Get()
{
void *result = nullptr;
while (result == nullptr)
{
// If the queue is empty, we must wait until it isn't.
if(::WaitForSingleObject(m_queue_not_empty, INFINITE) != WAIT_OBJECT_0)
{
ASSERT(FALSE);
}
// However, under some (rare) conditions we'll get here and find
// the queue is already empty again, so be prepared to loop.
::EnterCriticalSection(&m_lock);
if (!m_list.IsEmpty())
{
result = m_list.RemoveHead();
ASSERT(result != nullptr);
// The queue shouldn't be full at this point!
if (m_nCapacity <= 0) ASSERT(m_list.GetCount() < m_nCapacity);
SetEvent(m_queue_not_full);
if (!m_list.IsEmpty())
{
SetEvent(m_queue_not_empty);
}
}
::LeaveCriticalSection(&m_lock);
}
}