之前,多執行緒一些基本的東西,包括執行緒建立,互斥鎖,訊號量,我們都已經封裝,下面來看看訊息佇列
我們儘量少用系統自帶的訊息佇列(比如Linux的sys/msgqueue),那樣移植性不是很強,我們希望的訊息佇列,在訊息打包和提取都是用的標準的C++資料結構,當然,你也可以用連結串列或者是FIFO,那樣得先寫個連結串列或者FIFO出來。
我比較懶,直接用的C++的STL的deque,即雙埠佇列,這樣可靠性有保證,當然,速度可能沒有自己寫的連結串列快,但是沒關係,使用雙埠佇列還可以根據你自己的需要將資料插入到佇列頭或者佇列尾,這樣在訊息有優先順序的情況下還是有用的。
訊息佇列的核心作用其實很簡單,一個或多個執行緒往一個佇列後面堆資料,另外的一個執行緒從佇列前面取資料處理,基本操作也只有兩個,一個發,一個收,所以,我們定義訊息佇列基類為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class CMsgQueue { public: CMsgQueue(const char *pName=NULL); ~CMsgQueue(); //revice data from message queue virtual bool recvMsg(unsigned int &m_msg_code,void *&p_msg)=0; //send data to message queue virtual bool sendMsg(unsigned int m_msg_code,void *p_msg)=0; const char * getName(void) const { return msg_queue_name; } private: char *msg_queue_name; }; |
然後記得在COperratingSystemFactory里加上建立訊息佇列的方法:
1 2 3 4 5 6 7 8 9 |
class COperatingSystemFactory { public: static COperatingSystem *newOperatingSystem(); static CCountingSem *newCountingSem(unsigned int init); static CMutex *newMutex(const char *pName=NULL); static CMsgQueue *newMsgQueue(const char *pName=NULL); }; |
最後,從CMsgQueue繼承一個CLinuxMsgQueue,然後把recvMsg和sendMsg實現吧,實現的時候注意一下。
單純的操作雙埠FIFO不行,我們希望是接收訊息的時候如果沒有訊息,執行緒阻塞在那裡等待訊息直到有訊息到來才接著執行,所以,接收訊息的時候我們用了訊號量,阻塞在訊號量那裡,傳送訊息的時候操作完佇列,傳送一個訊號量出去。
其次,對於佇列的操作,我們希望是原子性的,不然一個正在收一個正在發就亂了,所以操作佇列的時候我們用互斥鎖來鎖一下,保證基本的原子性。
對應到具體的程式就是
1.為每個訊息佇列申請一個鎖,一個訊號量
1 2 3 4 5 6 |
CLinuxMsgQueue::CLinuxMsgQueue(const char *pName): CMsgQueue(pName) { p_mutex=COperatingSystemFactory::newMutex("Msg Mutex"); p_sem=COperatingSystemFactory::newCountingSem(0); } |
接收訊息的時候:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
bool CLinuxMsgQueue::recvMsg(unsigned int &m_msg_code,void *&p_msg) { bool result; Elements queue_element; p_sem->Get(); //通過訊號量阻塞在這裡,有訊息到達了才接著往下走 p_mutex->Lock(); //鎖定,保證原子性 //操作佇列 if (m_queue.empty()) { p_mutex-> UnLock (); return false; } queue_element = m_queue.front(); m_queue.pop_front(); m_msg_code = queue_element.msg_code; p_msg = queue_element.p_message; //操作佇列結束 p_mutex->UnLock(); //解除鎖定 return true; } |
傳送的時候也是類似的方式進行,這樣,一個最簡單訊息佇列就完成了。如果我們要使用訊息佇列的話,很簡單,在main.cpp中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int main() { //首先,新建一個訊息佇列 CMsgQueue *q=COperatingSystemFactory::newMsgQueue("B to A message Queue"); //新建兩個執行緒,TestThread和TestThreadB都是從CThread繼承下來的執行緒類 TestThread *a=new TestThread("A"); TestThreadB *b=new TestThreadB("B"); //將訊息佇列放到兩個執行緒實體的區域性變數中 a->setMsgQueue(q); b->setMsgQueue(q); //啟動執行緒 a->run(); b->run(); } |
當要在mainloop中傳送訊息的時候,只需要呼叫
1 |
p_msg_send->sendMsg(code, (void *)p_msg); //其中p_msg_send是b執行緒的區域性變數,實際指向的是之前新建的訊息佇列q |
github地址:
https://github.com/wyh267/Cplusplus_Thread_Lib
寫在後面的話:
當然,這個程式碼還非常不完整,整個程式碼量也沒有多少行,在這裡,我只是提供一個程式碼框架的方法,作為一個demo給大家參考,如果真的需要實際使用還有很多很多地方需要修改的,github上我的程式碼也不能在生產軟體中實際使用,在實際的專案中,我也實現了一個沒有任何第三方的執行緒庫,比這個複雜多了,還包括事件處理,等待超時,訊息廣播,訊息訂閱等模組,而且能執行在linux,ecos等多個平臺上,基本做到平臺無關了,但由於各種原因我也沒辦法將程式碼都公佈出來,這裡所說的這個框架只是專案中執行緒庫提取出來的非常少的一部分,同樣,也只是提供一種程式設計的設計思想,後面的東西希望大家各自有各自的發掘和完善,也許你看了以後,會提出更加強大和簡潔的框架。
另外,github上的程式碼我會繼續完善,將其他模組陸續加上,如果大家感興趣也可以跟我一起來完善,我儘量不使用之前實現過的執行緒庫的程式碼,避免不必要的麻煩。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!