筆者曾遇到這樣的需求:某軟件在運行中隨時有可能向外發送短信,一方面發送短信的設備是個獨佔資源,另一方面有多個線程要發短信。按照“把不穩定因素限定在一個實體中”的原則,自然就用一個專門的線程來操作短信設備,它的任務是從消息隊列中取出要發送的短信,通過短信設備發送出去。
這是比較常見的生產者-消費者模型,即一個模塊產生數據,另外模塊取得數據並進行處理。如何實現互斥?如何讓生產者和消費者都能夠方便的工作?
類似這樣的情況在編程中十分常見,採用消息隊列是很好的解決辦法。本文給出的消息隊列具有以下特色:
·消息的大小、結構是自由的,甚至可以是一個對象;
·消息隊列的長度(容納消息的個數)是可設定的,超過最大長度可以選擇丟棄或等待;
·新消息一般是放在隊列的最後面,也可以放在最前面;
·代碼少,思路簡潔,你可以根據情況擴展;
【設計思路】
設計思路其實很簡單,消息隊列類CHMQ是個模板類,用CList保存消息,用CriticalSection來實現互斥,用Event來控制消息隊列爲空或已滿的情況。提供以下幾個常用接口:
·Push() 在隊列尾增加消息,如果消息隊列已滿,根據設定或丟棄消息,或阻塞直到隊列不爲滿的時候。
·Insert() 在對列頭增加消息,其他同Push()。
·Pop() 從隊列頭中取消息,如果隊列爲空則阻塞,直到不爲空的時候。
·SetMaxCount() 設置消息隊列的最大長度,以及如果消息隊列滿的時候新消息的處理方式。 或丟棄消息,或阻塞直到隊列不爲滿的時候。
·GetMaxCount() 返回消息隊列的最大長度,如果沒有設置最大長度則返回-1。
·GetQueueCount() 返回目前消息隊列中的消息個數。
【擴展】
(1) 目前一個CHMQ對象僅支持一個消息隊列,可以擴展成多個消息隊列
(2) 消息隊列爲空時,Pop()一直阻塞到有新消息時才返回,可以擴展成超時返回
(3) 消息隊列已滿時,Push()和Insert()可能會阻塞到隊列不滿的時候,可以擴展成超時丟棄
#ifndef CHMQ_H
#define CHMQ_H
#include
#include
template
class CHMQ
{
public:
CHMQ()
{
m_nMax = -1;
m_bDrop = FALSE;
::InitializeCriticalSection(&m_lock);
m_hPushEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
ASSERT(m_hPushEvent != NULL);
m_hPopEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
ASSERT(m_hPopEvent != NULL);
}
~CHMQ()
{
::DeleteCriticalSection(&m_lock);
::CloseHandle(m_hPushEvent);
::CloseHandle(m_hPopEvent);
m_list.RemoveAll();
}
// 隊列尾增加消息,如果消息隊列已滿,根據設定或丟棄消息,或阻塞直到隊列不爲滿的時候。
void Push(TYPE& type)
{
::EnterCriticalSection(&m_lock);
// 如果消息隊列已滿
if ( m_nMax > 0 && m_list.GetCount() >= m_nMax)
{
::ResetEvent(m_hPushEvent);
::LeaveCriticalSection(&m_lock);
if( m_bDrop )
return;
if ( ::WaitForSingleObject(m_hPushEvent, INFINITE) != WAIT_OBJECT_0)
ASSERT(FALSE);
::EnterCriticalSection(&m_lock);
}
m_list.AddTail(type);
::SetEvent(m_hPopEvent);
::LeaveCriticalSection(&m_lock);
}
// 隊列頭增加消息,如果消息隊列已滿,根據設定或丟棄消息,或阻塞直到隊列不爲滿的時候。
void Insert(TYPE& type)
{
::EnterCriticalSection(&m_lock);
// 如果消息隊列已滿
if ( m_nMax > 0 && m_list.GetCount() >= m_nMax)
{
::ResetEvent(m_hPushEvent);
::LeaveCriticalSection(&m_lock);
if( m_bDrop )
return;
if ( ::WaitForSingleObject(m_hPushEvent, INFINITE) != WAIT_OBJECT_0)
ASSERT(FALSE);
::EnterCriticalSection(&m_lock);
}
m_list.AddHead(type);
::SetEvent(m_hPopEvent);
::LeaveCriticalSection(&m_lock);
}
// 從隊列頭中取消息,如果隊列爲空則阻塞,直到不爲空的時候。
TYPE Pop()
{
TYPE type;
::EnterCriticalSection(&m_lock);
// 如果隊列爲空
if (m_list.IsEmpty())
{
::ResetEvent(m_hPopEvent);
::LeaveCriticalSection(&m_lock);
if ( ::WaitForSingleObject(m_hPopEvent, INFINITE) != WAIT_OBJECT_0)
ASSERT(FALSE);
::EnterCriticalSection(&m_lock);
}
type = m_list.RemoveHead();
::SetEvent(m_hPushEvent);
::LeaveCriticalSection(&m_lock);
return type;
}
// 返回目前消息隊列中的消息個數。
int GetQueueCount()
{
int nCount = 0;
::EnterCriticalSection(&m_lock);
nCount = m_list.GetCount();
::LeaveCriticalSection(&m_lock);
return nCount;
}
// 設置消息隊列的最大長度,以及如果消息隊列滿的時候新消息的處理方式。
// 或丟棄消息,或阻塞直到隊列不爲滿的時候。
void SetMaxCount(int nMax = -1, BOOL bDrop = FALSE)
{
::EnterCriticalSection(&m_lock);
m_nMax = nMax;
m_bDrop = bDrop;
::LeaveCriticalSection(&m_lock);
}
// 返回消息隊列的最大長度,如果沒有設置最大長度則返回-1。
int GetMaxCount()
{
::EnterCriticalSection(&m_lock);
::LeaveCriticalSection(&m_lock);
return m_nMax;
}
private:
CRITICAL_SECTION m_lock;
CList m_list;
HANDLE m_hPopEvent;
HANDLE m_hPushEvent;
int m_nMax;
BOOL m_bDrop;
};
#endif // CHMQ_H