生產者-消費者模型的解決思路——自建隊列

筆者曾遇到這樣的需求:某軟件在運行中隨時有可能向外發送短信,一方面發送短信的設備是個獨佔資源,另一方面有多個線程要發短信。按照“把不穩定因素限定在一個實體中”的原則,自然就用一個專門的線程來操作短信設備,它的任務是從消息隊列中取出要發送的短信,通過短信設備發送出去。

這是比較常見的生產者-消費者模型,即一個模塊產生數據,另外模塊取得數據並進行處理。如何實現互斥?如何讓生產者和消費者都能夠方便的工作?

類似這樣的情況在編程中十分常見,採用消息隊列是很好的解決辦法。本文給出的消息隊列具有以下特色:
    ·消息的大小、結構是自由的,甚至可以是一個對象;
    ·消息隊列的長度(容納消息的個數)是可設定的,超過最大長度可以選擇丟棄或等待;
    ·新消息一般是放在隊列的最後面,也可以放在最前面;
    ·代碼少,思路簡潔,你可以根據情況擴展;

【設計思路】
設計思路其實很簡單,消息隊列類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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章