考え直してみると、セマフォを明示的に実装する必要はほとんどありません。代わりに、イベントを使用してセマフォを実装する方法を考え、その方法で問題にアプローチしてください。私の最初の試みは手動リセットイベントを使用しましたが、これは非効率的でしたが明らかに正しいものでした。その後、最適化しました。
これらのコードフラグメントのいずれもデバッグ(またはコンパイル)していないことに注意してください。ただし、正しいアイデアが得られるはずです。手動リセットバージョンは次のとおりです。
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);
}
}