QT线程使用收集示例
關(guān)于多線程問(wèn)題:
? ? ?Qt和Boost做跨平臺(tái)的線程封裝,OpenMP主要做并行計(jì)算,讓不精通多線程的人也能高效地利用CPU的計(jì)算能力。
個(gè)人傾向于用boost.thread,?boost.mpi.
? ? ? 一個(gè)openMP的指南網(wǎng)頁(yè):http://www.cnblogs.com/me115/archive/2011/01/25/1944567.html
? ? ? 內(nèi)容非常詳細(xì)。
? ? ? ?關(guān)于OpenMP創(chuàng)建線程中的鎖及原子操作性能比較:
? ??? 原文鏈接: ?http://blog.csdn.net/drzhouweiming/article/details/1689853
? ? ? ?一片有關(guān)圖像特征提取的openMp編程:http://www.cnblogs.com/yangyangcv/archive/2012/03/23/2413335.html
(1):實(shí)現(xiàn)QT簡(jiǎn)單多線程
? 在qt程序中,我們經(jīng)常會(huì)遇到計(jì)算密集型操作或者存在大量I/O操作的時(shí)候,GUI就會(huì)發(fā)生凍結(jié)現(xiàn)象,并且會(huì)無(wú)法響應(yīng).仔細(xì)分析一下為什么會(huì)出現(xiàn)這種情況:因?yàn)镚UI是一個(gè)主線程,而我們?nèi)绻延?jì)算密集型或者I/O操作放到主線程中去執(zhí)行,(換句話說(shuō)我們采用單一線程方案),那我們只能等待事件執(zhí)行完之后GUI才能進(jìn)行響應(yīng)。
??????1. 有種比較簡(jiǎn)單的辦法是調(diào)用QApplication::processEvents(),但是屬于治標(biāo)不治本的方法。這個(gè)函數(shù)的作用主要就是處理qt在計(jì)算事件中處理某個(gè)點(diǎn)懸掛的事件。
????? qt文檔中有句話影響比較深刻:In event you are running a local loop which calls this function continuously, without an event loop, the?DeferredDelete?events will not be processed.
????? 2. 從單一線程的方式改為多線程的方式。
????? qt應(yīng)用程序中,多線程的操作實(shí)現(xiàn)是非常簡(jiǎn)單的:只需要子類化QThread,實(shí)現(xiàn)他的run()函數(shù)即可。但是多線程如果在單個(gè)CPU中運(yùn)行相對(duì)單線程可能會(huì)慢點(diǎn),多個(gè)CPU的話其優(yōu)勢(shì)會(huì)展現(xiàn)出來(lái)。下面我就實(shí)現(xiàn)一個(gè)多線程的簡(jiǎn)單案例:
//Thread.h#ifndef THREAD_H #define THREAD_H#include <QThread> #include<QDebug>class Thread : public QThread {Q_OBJECTpublic:Thread(int _type);~Thread();void run(); private:int type; //線程類型1或2};#endif // THREAD_H//Thread.cpp#include "Thread.h" Thread::Thread(int _type) :type(_type) {}Thread::~Thread() { } void Thread::run() {int count=5;while(count>0){qDebug()<<"Thread"<<type; //循環(huán)5次打印類型count--;} }//main.cpp main函數(shù)中調(diào)用#include <QtCore/QCoreApplication> #include "Thread.h"int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);Thread *pThread1=new Thread(1); //線程1pThread1->start();Thread *pThread2=new Thread(2); //線程2pThread2->start();return a.exec(); }兩次運(yùn)行結(jié)果:
(2):實(shí)現(xiàn)同步QT ftp
? ?原文鏈接:http://mobile.51cto.com/symbian-268682.htm
在Qt中實(shí)現(xiàn)QThread線程同步QFtp?,對(duì)于QFtp,它是一個(gè)用來(lái)實(shí)現(xiàn)FTP協(xié)議的類,詳情查閱資料。接觸Qt沒(méi)有多長(zhǎng)時(shí)間,但簡(jiǎn)單幾個(gè)小例子已經(jīng)讓我感受到Qt在C++運(yùn)用方面的強(qiáng)大。寫(xiě)了一個(gè)小程序,需要在一個(gè)單獨(dú)的線程中使用QFtp來(lái)獲取FTP服務(wù)器上面的文件。FTP是FileZilla。
有兩個(gè)問(wèn)題我比較關(guān)心:
(1)QThread到底如何使用
(2)QFtp是Async(異步)操作,也就是說(shuō)例如connectToHost這樣的函數(shù)都是立刻返回,當(dāng)操作完成后QFtp會(huì)發(fā)出signal。然而既然我的Ftp操作是在一個(gè)單獨(dú)的線程,我想寫(xiě)一個(gè)函數(shù)downloadFtpFile() 來(lái)完成從connect到login到下載文件等一系列的操作,然后再返回。相當(dāng)于我需要Sync(同步)的操作,所以需要等待(block)每個(gè)Ftp命令的結(jié)果。
在該文章最后有一個(gè)推薦的使用QThread的方法。我在這里想補(bǔ)充一點(diǎn):obj.moveToThread(&thread); 這句話將obj從主線程移動(dòng)到了thread對(duì)象所在的線程。但如果obj的對(duì)象里面有其他的變量,那么這些變量是在主線程中生成的。所以如果這些變量中有類變量,不能將obj的this指針作為parent傳給他們。
對(duì)于第二個(gè)問(wèn)題,我使用了QSemaphore類來(lái)完成我的block和同步操作:在slot函數(shù)里面接收QFtp命令執(zhí)行結(jié)果的signal,釋放信號(hào),同時(shí)downloadFtpFile()函數(shù)里在調(diào)用完每一個(gè)QFtp異步命令后等待信號(hào)。在有點(diǎn)令人失望的是QSemaphore在通過(guò)tryAcquire()等待信號(hào)的時(shí)候是不處理事件event的。但是我需要在等待的時(shí)候程序也能觸發(fā)slot,告訴我當(dāng)前命令的執(zhí)行情況。所以我使用了一個(gè)小循環(huán),里面調(diào)用qApp->processEvents();來(lái)讓我的slot函數(shù)被觸發(fā)。下面是代碼例子(只是樣例,并不完全符合C++語(yǔ)法):
首先是我的下載Ftp文件的函數(shù):
downloadFtpFile () //該函數(shù)在單獨(dú)線程里執(zhí)行 { int m_idFtpOp; // 該變量用來(lái)存放每一個(gè)QFtp命令I(lǐng)D int nVal; QFtp*pFtp=newQFtp (this); // 生成QFtp工具對(duì)象 connect (pFtp,SIGNAL(listInfo(QUrlInfo)),this,SLOT(slotFtpListInfo(QUrlInfo))); // 我們需要listinfo,因?yàn)槲覀冃枰螺dftp所有當(dāng)前目錄文件 connect (pFtp,SIGNAL(commandFinished(int,bool)),this,SLOT(slotFtpCmdFinished(int,bool))); // 每個(gè)QFtp命令完成之后,會(huì)發(fā)出commandFinished信號(hào),我們?cè)诓酆瘮?shù)中處理該信號(hào) m_idFtpOp = pFtp->connectToHost (<FTP地址>, 21); // 連接到遠(yuǎn)程FTP Server bRet=false; nVal=100; while (bRet == false) // 使用nVal變量來(lái)做一個(gè)10000ms(10s)的超時(shí) { nVal--; if (nVal == 0) break; qApp->processEvents(); // 這里每100ms處理一次event,使slot函數(shù)能夠被調(diào)用 bRet=m_SemOp.tryAcquire (1,100); // 等待信號(hào)100ms } if (!bRet || m_bFtpOpError) // 如果超時(shí),或者slot函數(shù)中將m_bFtpOpError置成true,則關(guān)閉Ftp,返回錯(cuò)誤 { pFtp->abort(); pFtp->deleteLater(); return ERRCODE_FCC_FTP_CONN_TIMEOUT; } }
下面是槽函數(shù)
以上的代碼只演示了對(duì)QFtp第一個(gè)命令connectToHost的等待過(guò)程。下面的login,list,get等操作都使用這個(gè)方法。
注意:在此例中,QFtp是在當(dāng)前線程生成的,所以信號(hào)listInfo(QUrlInfo)的connect方式是direct連接。如果QFtp是在另一個(gè)線程生成(比如說(shuō)是在函數(shù)downloadFtpFile所在類的構(gòu)造函數(shù)中),那么第一:不能將this指針作為parent傳給QFtp對(duì)象,第二:需要使用qRegisterMetaType<QUrlInfo>("QUrlInfo");來(lái)注冊(cè)QUrlInfo類,因?yàn)樾盘?hào)發(fā)射與接收在不通的線程中,信號(hào)使用queued的方式。如果不注冊(cè)QURlInfo類,會(huì)在運(yùn)行時(shí)動(dòng)態(tài)報(bào)告錯(cuò)誤。
總結(jié):本文介紹的是在Qt中如何實(shí)現(xiàn)QThread線程同步QFtp ,看過(guò)本文之后,如果對(duì)于QThread不了解的話,那么請(qǐng)參考Qt中QThread使用方法這篇文章。使用本文介紹的方法,可以在獨(dú)立的線程中用同步的方式使用QFtp。在某些場(chǎng)合,尤其是采用應(yīng)答機(jī)制的系統(tǒng)中,這樣的實(shí)現(xiàn)可以很大程度上簡(jiǎn)化程序流程。
(3):淺談QT多線程
原文鏈接:http://mobile.51cto.com/symbian-268343.htm
Qt?作為一種基于 C++ 的跨平臺(tái) GUI 系統(tǒng),能夠提供給用戶構(gòu)造圖形用戶界面的強(qiáng)大功能。為了滿足用戶構(gòu)造復(fù)雜圖形界面系統(tǒng)的需求,Qt?提供了豐富的多線程編程支持。從 2.2 版本開(kāi)始,Qt?主要從下面三個(gè)方面對(duì)多線程編程提供支持:一、構(gòu)造了一些基本的與平臺(tái)無(wú)關(guān)的線程類;二、提交用戶自定義事件的 Thread-safe 方式;三、多種線程間同步機(jī)制,如信號(hào)量,全局鎖。這些都給用戶提供了極大的方便。不過(guò),在某些情況下,使用定時(shí)器機(jī)制能夠比利用 Qt 本身的多線程機(jī)制更方便地實(shí)現(xiàn)所需要的功能,同時(shí)也避免了不安全的現(xiàn)象發(fā)生。本文不僅對(duì)Qt?中的多線程支持機(jī)制進(jìn)行了討論,還著重探討了利用定時(shí)器機(jī)制模擬多線程編程的方法。
1、系統(tǒng)對(duì)多線程編程的支持
不同的平臺(tái)對(duì)Qt 的多線程支持方式是不同的。當(dāng)用戶在 Windows 操作系統(tǒng)上安裝 Qt 系統(tǒng)時(shí),線程支持是編譯器的一個(gè)選項(xiàng),在 Qt 的 mkfiles 子目錄中包括了不同種類編譯器的編譯文件,其中帶有 -mt 后綴的文件才是支持多線程的。
而在 Unix 操作系統(tǒng)中,線程的支持是通過(guò)在運(yùn)行 configure 腳本文件時(shí)添加 -thread 選項(xiàng)加入的。安裝過(guò)程將創(chuàng)建一個(gè)獨(dú)立的庫(kù),即 libqt-mt,因此要支持多線程編程時(shí),必須與該庫(kù)鏈接(鏈接選項(xiàng)為-lqt-mt),而不是與通常的 Qt 庫(kù)(-lqt)鏈接。
另外,無(wú)論是何種平臺(tái),在增加線程支持時(shí)都需要定義宏 QT_THREAD_SUPPORT(即增加編譯選項(xiàng)-DQT_THREAD_SUPPORT)。在 Windows 操作系統(tǒng)中,這一點(diǎn)通常是在 qconfig.h 文件中增加一個(gè)選項(xiàng)來(lái)實(shí)現(xiàn)的。而在 Unix 系統(tǒng)中通常添加在有關(guān)的 Makefile 文件中。
2、Qt中的線程類
在 Qt 系統(tǒng)中與線程相關(guān)的最重要的類當(dāng)然是 QThread 類,該類提供了創(chuàng)建一個(gè)新線程以及控制線程運(yùn)行的各種方法。線程是通過(guò) QThread::run() 重載函數(shù)開(kāi)始執(zhí)行的,這一點(diǎn)很象 Java 語(yǔ)言中的線程類。在 Qt 系統(tǒng)中,始終運(yùn)行著一個(gè)GUI 主事件線程,這個(gè)主線程從窗口系統(tǒng)中獲取事件,并將它們分發(fā)到各個(gè)組件去處理。在 QThread 類中還有一種從非主事件線程中將事件提交給一個(gè)對(duì)象的方法,也就是 QThread::postEvent()方法,該方法提供了 Qt 中的一種 Thread-safe 的事件提交過(guò)程。提交的事件被放進(jìn)一個(gè)隊(duì)列中,然后 GUI 主事件線程被喚醒并將此事件發(fā)給相應(yīng)的對(duì)象,這個(gè)過(guò)程與一般的窗口系統(tǒng)事件處理過(guò)程是一樣的。值得注意的是,當(dāng)事件處理過(guò)程被調(diào)用時(shí),是在主事件線程中被 調(diào)用的,而不是在調(diào)用QThread::postEvent 方法的線程中被調(diào)用。比如用戶可以從一個(gè)線程中迫使另一個(gè)線程重畫(huà)指定區(qū)域:
然而,只有一個(gè)線程類是不夠的,為編寫(xiě)出支持多線程的程序,還需要實(shí)現(xiàn)兩個(gè)不同的線程對(duì)共有數(shù)據(jù)的互斥訪問(wèn),因此 Qt 還提供了 QMutex 類,一個(gè)線程在訪問(wèn)臨界數(shù)據(jù)時(shí),需要加鎖,此時(shí)其他線程是無(wú)法對(duì)該臨界數(shù)據(jù)同時(shí)加鎖的,直到前一個(gè)線程釋放該臨界數(shù)據(jù)。通過(guò)這種方式才能實(shí)現(xiàn)對(duì)臨界數(shù)據(jù)的 原子操作。
除此之外,還需要一些機(jī)制使得處于等待狀態(tài)的線程在特定情況下被喚醒。QWaitCondition 類就提供了這種功能。當(dāng)發(fā)生特定事件時(shí),QWaitCondition 將喚醒等待該事件的所有線程或者喚醒任意一個(gè)被選中的線程。
3、用戶自定義事件在多線程編程中的應(yīng)用
在 Qt 系統(tǒng)中,定義了很多種類的事件,如定時(shí)器事件、鼠標(biāo)移動(dòng)事件、鍵盤(pán)事件、窗口控件事件等。通常,事件都來(lái)自底層的窗口系統(tǒng),Qt 的主事件循環(huán)函數(shù)從系統(tǒng)的事件隊(duì)列中獲取這些事件,并將它們轉(zhuǎn)換為 QEvent,然后傳給相應(yīng)的 QObjects 對(duì)象。
除此之外,為了滿足用戶的需求,Qt 系統(tǒng)還提供了一個(gè) QCustomEvent 類,用于用戶自定義事件,這些自定義事件可以利用 QThread::postEvent() 或者QApplication::postEvent() 被發(fā)給各種控件或其他 QObject 實(shí)例,而 QWidget 類的子類可以通過(guò) QWidget::customEvent() 事件處理函數(shù)方便地接收到這些自定義的事件。需要注意的是:QCustomEvent 對(duì)象在創(chuàng)建時(shí)都帶有一個(gè)類型標(biāo)識(shí) id 以定義事件類型,為了避免與 Qt 系統(tǒng)定義的事件類型沖突,該 id 值應(yīng)該大于枚舉類型 QEvent::Type 中給出的 "User" 值。
在下面的例子中,顯示了多線程編程中如何利用用戶自定義事件類。
UserEvent類是用戶自定義的事件類,其事件標(biāo)識(shí)為346798,顯然不會(huì)與系統(tǒng)定義的事件類型沖突。
UserThread類是由QThread類繼承而來(lái)的子類,在該類中除了定義有關(guān)的變量和線程控制函數(shù)外,最主要的是定義線程的啟動(dòng) 函數(shù)UserThread::run(),在該函數(shù)中創(chuàng)建了一個(gè)用戶自定義事件UserEvent,并利用QThread類的postEvent函數(shù)提交 該事件給相應(yīng)的接收對(duì)象。??????
UserWidget類是用戶定義的用于接收自定義事件的QWidget類的子類,該類利用slotGo()函數(shù)創(chuàng)建了一個(gè)新的線程 recv(UserThread類),當(dāng)收到相應(yīng)的自定義事件(即id為346798)時(shí),利用customEvent函數(shù)對(duì)事件進(jìn)行處理。
在這個(gè)例子中,UserWidget對(duì)象中創(chuàng)建了新的線程UserThread,用戶可以利用這個(gè)線程實(shí)現(xiàn)一些周期性的處理(如接收底 層發(fā)來(lái)的消息等),一旦滿足特定條件就提交一個(gè)用戶自定義的事件,當(dāng)UserWidget對(duì)象收到該事件時(shí),可以按需求做出相應(yīng)的處理,而一般情況 下,UserWidget對(duì)象可以正常地執(zhí)行某些例行處理,而完全不受底層消息的影響。
4、利用定時(shí)器機(jī)制實(shí)現(xiàn)多線程編程
為了避免Qt系統(tǒng)中多線程編程帶來(lái)的問(wèn)題,還可以使用系統(tǒng)中提供的定時(shí)器機(jī)制來(lái)實(shí)現(xiàn)類似的功能。定時(shí)器機(jī)制將并發(fā)的事件串行 化,簡(jiǎn)化了對(duì)并發(fā)事件的處理,從而避免了thread-safe方面問(wèn)題的出現(xiàn)。
在下面的例子中,同時(shí)有若干個(gè)對(duì)象需要接收底層發(fā)來(lái)的消息(可以通過(guò)Socket、FIFO等進(jìn)程間通信機(jī)制),而消息是隨機(jī)收到的, 需要有一個(gè)GUI主線程專門(mén)負(fù)責(zé)接收消息。當(dāng)收到消息時(shí)主線程初始化相應(yīng)對(duì)象使之開(kāi)始處理,同時(shí)返回,這樣主線程就可以始終更新界面顯示并接收外界發(fā)來(lái)的 消息,達(dá)到同時(shí)對(duì)多個(gè)對(duì)象的控制;另一方面,各個(gè)對(duì)象在處理完消息后需要通知GUI主線程。對(duì)于這個(gè)問(wèn)題,可以利用第3節(jié)中的用戶自定義事件的方法,在主 線程中安裝一個(gè)事件過(guò)濾器,來(lái)捕捉從各個(gè)對(duì)象中發(fā)來(lái)的自定義事件,然后發(fā)出信號(hào)調(diào)用主線程中的一個(gè)槽函數(shù)。
另外,也可以利用Qt中的定時(shí)器機(jī)制實(shí)現(xiàn)類似的功能,而又不必?fù)?dān)心Thread-safe問(wèn)題。下面就是有關(guān)的代碼部分:
在用戶定義的Server類中創(chuàng)建和啟動(dòng)了定時(shí)器,并利用connect函數(shù)將定時(shí)器超時(shí)與讀取設(shè)備文件數(shù)據(jù)相關(guān)聯(lián):
slotReadFile函數(shù)負(fù)責(zé)在定時(shí)器超時(shí)時(shí),從文件中讀取數(shù)據(jù),然后重新啟動(dòng)定時(shí)器:
在該程序中,利用了類似輪循的方式定時(shí)對(duì)用戶指定的設(shè)備文件進(jìn)行讀取,根據(jù)讀到的數(shù)據(jù)內(nèi)容將信息發(fā)送到各個(gè)相應(yīng)的對(duì)象。用戶可以在自己 的GUI主線程中創(chuàng)建一個(gè)Server類,幫助實(shí)現(xiàn)底層的消息接收過(guò)程,而本身仍然可以處理諸如界面顯示的問(wèn)題。當(dāng)各個(gè)對(duì)象完成處理后,通過(guò)重新啟動(dòng)定時(shí) 器繼續(xù)進(jìn)行周期性讀取底層設(shè)備文件的過(guò)程。當(dāng)然,這種方法適合于各對(duì)象對(duì)事件的處理時(shí)間較短,而底層設(shè)備發(fā)來(lái)消息的頻率又相對(duì)較慢的情況。在這種情況下, 上述方法完全可以滿足用戶的需求,而又避免了處理一些與線程并發(fā)有關(guān)的復(fù)雜問(wèn)題。
當(dāng)然,利用定時(shí)器機(jī)制實(shí)現(xiàn)多線程編程在某些方面具有一定的局限性,有關(guān)到底如何實(shí)現(xiàn)多線程編程,如何編寫(xiě)出效率更高的代碼,還有待于開(kāi) 發(fā)者進(jìn)一步研究和探討。
(4):示例解析QT線程同步
Qt?線程同步實(shí)例介紹是本文介紹的內(nèi)容,在Qt中使用線程,沒(méi)有Mfc中那么繁瑣,它提供了QThread線程類,提供了創(chuàng)建一個(gè)新的方法。線程通過(guò)重載QThread::run()函數(shù)來(lái)完成其操作的,這一點(diǎn)與Java中的線程類相似。 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的繼承自QThread的用戶線程類,代碼如下。 1.?????????? class?Thread?:?public?QThread? ? 2.?????????? { ? 3.?????????? public: ? 4.?????????? ????Thread(); ? 5.?????????? ????void?stop(); ? 6.?????????? protected: ? 7.?????????? ????virtual?void?run(); ? 8.?????????? private: ? 9.?????????? ????bool?m_stop; ? 10.?????? }; ? 11.?????? Thread::Thread() ? 12.?????? { ? 13.?????? ????m_stop?=?false; ? 14.?????? } ? 15.?????? void?Thread::stop() ? 16.?????? { ? 17.?????? ????m_stop?=?true; ? 18.?????? } ? 19.?????? void?Thread::run() ? 20.?????? { ? 21.?????? ????while?(!m_stop) ? 22.?????? ????{ ? 23.?????? ????????sleep(1); ? 24.?????? ????????qDebug("vic.MINg!"); ? 25.?????? ????} ? 26.?????? ????qDebug("end!"); ? 27.?????? }? 在以上的示例中可以看出,線程的編寫(xiě)并不難! 啟動(dòng)線程的時(shí)候可以,調(diào)用函數(shù)QThread::start(),開(kāi)始Thread線程對(duì)象。 停止線程的時(shí)候可以,調(diào)用函數(shù)QThread::terminate(),但是terminate()函數(shù)并不會(huì)立刻終止線程,該線程何時(shí)終止取決于操作系統(tǒng)的調(diào)度策略。需要注意的是,terminate()函數(shù)過(guò)于毒辣,它可能在線程執(zhí)行的任意一步終止執(zhí)行,從而產(chǎn)生不可預(yù)知的后果(如修改某個(gè)重要數(shù)據(jù)時(shí)),另外,它也沒(méi)有給線程任何清理現(xiàn)場(chǎng)的機(jī)會(huì)(如釋放內(nèi)存和鎖等)。 因此,停止線程可以,如上代碼所示,手寫(xiě)函數(shù)stop(),使其線程柔和的退出。 線程停止后,應(yīng)調(diào)用QThread::wait()函數(shù),它使的線程阻塞等待直到退出或超時(shí)。 貌似在Unix或Linux下編譯多線程應(yīng)用程序還必須在.pro文件中加入如下一行,它告訴qmake使用Qt庫(kù)中的線程版本。Windows上,Qt庫(kù)默認(rèn)就是線程的。 CONFIG += thread 介紹完了線程的創(chuàng)建,接下來(lái)走入正題了,多線程應(yīng)用程序的一個(gè)最普通的需求就是同步幾個(gè)線程。Qt提供了以下幾個(gè)類來(lái)完成這一點(diǎn):QMutex、QMutexLocker、QSemphore、QWaitCondition。 當(dāng)然可能還包含QReadWriteLocker、QReadLocker、QWriteLocker,但線程同步是應(yīng)用很少,這里只做簡(jiǎn)單的講解! QMutex、QMutexLocker QMutex類提供了一個(gè)保護(hù)一段臨界區(qū)代碼的方法,他每次只允許一個(gè)線程訪問(wèn)這段臨界區(qū)代碼。QMutex::lock()函數(shù)用來(lái)鎖住互斥量,如果互斥量處于解鎖狀態(tài),當(dāng)前線程就會(huì)立即抓住并鎖定它;否則當(dāng)前線程就會(huì)被阻塞,直到持有這個(gè)互斥量的線程對(duì)其解鎖。線程調(diào)用lock()函數(shù)后就會(huì)持有這個(gè)互斥量直到調(diào)用unlock()操作為止。QMutex還提供了一個(gè)tryLock()函數(shù),如果互斥量已被鎖定,就立即返回。 現(xiàn)在使用QMutex保護(hù)上面的線程類的m_stop布爾變量,雖然沒(méi)啥用,但這里的目的只是為了演示下QMutex的用法~~ 1.?????????? //thread.h頭文件,添加互斥量對(duì)象? 2.?????????? private: ? 3.?????????? ????... ? 4.?????????? ????QMutex?mutex; ? 5.?????????? }; ? 6.?????????? void?Thread::run() ? 7.?????????? { ? 8.?????????? ????forever?{ ? 9.?????????? ????????mutex.lock(); ? 10.?????? ????????if?(m_stop)?{ ? 11.?????? ????????????m_stop?=?false; ? 12.?????? ????????????mutex.unlock(); ? 13.?????? ????????????break; ? 14.?????? ????????} ? 15.?????? ????????mutex.unlock(); ? 16.?????? ????????qDebug("vic.MINg!"); ? 17.?????? ????} ? 18.?????? ????qDebug("end!"); ? 19.?????? } ? 20.?????? void?Thread::stop() ? 21.?????? { ? 22.?????? ????mutex.lock(); ? 23.?????? ????m_stop?=?true; ? 24.?????? ????mutex.unlock(); ? 25.?????? }? 在這里QMutex能夠完全完成互斥操作,但是有些情況下QMutex類是無(wú)法某些特定的互斥操作的,下面舉個(gè)例子: 這里我們把void stop()函數(shù),重新定義下,讓他以布爾形式返回,實(shí)際也沒(méi)有啥用...只為示例的演示效果~~ 1.?????????? bool?Thread::stop() ? 2.?????????? { ? 3.?????????? ????m_stop?=?true; ? 4.?????????? ????return?m_stop; ? 5.?????????? }? 現(xiàn)在問(wèn)題出來(lái)了,如果要在stop()函數(shù)中使用mutex進(jìn)行互斥操作,但unlock()操作寫(xiě)在那里?unlock()操作卻不得不再return之后,從而導(dǎo)致unlock()操作永遠(yuǎn)也無(wú)法執(zhí)行... Qt提供了QMutexLocker類何以簡(jiǎn)化互斥量的處理,它在構(gòu)造函數(shù)中接受一個(gè)QMutex對(duì)象作為參數(shù)并將其鎖定,在析構(gòu)函數(shù)中解鎖這個(gè)互斥量。 這樣可以像下面這樣重新編寫(xiě)stop()函數(shù): 1.?????????? bool?Thread::stop() ? 2.?????????? { ? 3.?????????? ????QMutexLocker?locker(&mutex); ? 4.?????????? ????m_stop?=?true; ? 5.?????????? ????return?m_stop; ? 6.?????????? }? QReadWriteLocker、QReadLocker、QWriteLocker 下面是一段對(duì)QReadWriteLocker類的對(duì)象進(jìn)行,讀寫(xiě)鎖的操作,比較簡(jiǎn)單,這里也不多做講解了,自己看吧 :) 1.?????????? MyData?data; ? 2.?????????? QReadWriteLock?lock; ? 3.?????????? void?ReaderThread::run() ? 4.?????????? { ? 5.?????????? ????... ? 6.?????????? ????lock.lockForRead(); ? 7.?????????? ????access_data_without_modifying_it(&data); ? 8.?????????? ????lock.unlock(); ? 9.?????????? ????... ? 10.?????? } ? 11.?????? void?WriterThread::run() ? 12.?????? { ? 13.?????? ????... ? 14.?????? ????lock.lockForWrite(); ? 15.?????? ????modify_data(&data); ? 16.?????? ????lock.unlock(); ? 17.?????? ????... ? 18.?????? }? QSemphore Qt中的信號(hào)量是由QSemaphore類提供的,信號(hào)量可以理解為互斥量功能的擴(kuò)展,互斥量只能鎖定一次而信號(hào)量可以獲取多次,它可以用來(lái)保護(hù)一定數(shù)量的同種資源。 acquire(n)函數(shù)用于獲取n個(gè)資源,當(dāng)沒(méi)有足夠的資源時(shí)調(diào)用者將被阻塞直到有足夠的可用資源。release(n)函數(shù)用于釋放n個(gè)資源。 QSemaphore類還提供了一個(gè)tryAcquire(n)函數(shù),在沒(méi)有足夠的資源是該函數(shù)會(huì)立即返回。 一個(gè)典型的信號(hào)量應(yīng)用程序是在兩個(gè)線程間傳遞一定數(shù)量的數(shù)據(jù)(DataSize),而這兩個(gè)線程使用一定大小(BufferSize)的共享循環(huán)緩存。 1.?????????? const?int?DataSize?=?100000; ? 2.?????????? const?int?BufferSize?=?4096; ? 3.?????????? char?buffer[BufferSize];? 生產(chǎn)者線程向緩存中寫(xiě)入數(shù)據(jù),直到它到達(dá)終點(diǎn),然后在起點(diǎn)重新開(kāi)始,覆蓋已經(jīng)存在的數(shù)據(jù)。消費(fèi)者線程讀取前者產(chǎn)生的數(shù)據(jù)。 生產(chǎn)者、消費(fèi)者實(shí)例中對(duì)同步的需求有兩處,如果生產(chǎn)者過(guò)快的產(chǎn)生數(shù)據(jù),將會(huì)覆蓋消費(fèi)者還沒(méi)有讀取的數(shù)據(jù),如果消費(fèi)者過(guò)快的讀取數(shù)據(jù),將越過(guò)生產(chǎn)者并且讀取到一些垃圾數(shù)據(jù)。 解決這個(gè)問(wèn)題的一個(gè)有效的方法是使用兩個(gè)信號(hào)量: 1.?????????? QSemaphore?freeSpace(BufferSize); ? 2.?????????? QSemaphore?usedSpace(0);? freeSpace信號(hào)量控制生產(chǎn)者可以填充數(shù)據(jù)的緩存部分。usedSpace信號(hào)量控制消費(fèi)者可以讀取的區(qū)域。這兩個(gè)信號(hào)量是互補(bǔ)的。其中freeSpace信號(hào)量被初始化為BufferSize(4096),表示程序一開(kāi)始有BufferSize個(gè)緩沖區(qū)單元可被填充,而信號(hào)量usedSpace被初始化為0,表示程序一開(kāi)始緩沖區(qū)中沒(méi)有數(shù)據(jù)可供讀取。 對(duì)于這個(gè)實(shí)例,每個(gè)字節(jié)就看作一個(gè)資源,實(shí)際應(yīng)用中常會(huì)在更大的單位上進(jìn)行操作,從而減小使用信號(hào)量帶來(lái)的開(kāi)銷。 1.?????????? void?Producer::run() ? 2.?????????? { ? 3.?????????? ????for?(int?i?=?0;?i?<?DataSize;?++i)?{ ? 4.?????????? ????????freeSpace.acquire(); ? 5.?????????? ????????buffer[i?%?BufferSize]?=?"MING"[uint(rand())?%?4]; ? 6.?????????? ????????usedSpace.release(); ? 7.?????????? ????} ? 8.?????????? }? 在生產(chǎn)者中,我們從獲取一個(gè)“自由的”字節(jié)開(kāi)始。如果緩存被消費(fèi)者還沒(méi)有讀取的數(shù)據(jù)填滿,acquire()的調(diào)用就會(huì)阻塞,直到消費(fèi)者已經(jīng)開(kāi)始消耗這些數(shù)據(jù)為止。一旦我們已經(jīng)獲取了這個(gè)字節(jié),我們就用一些隨機(jī)數(shù)據(jù)("M"、"I"、"N"或"G")填充它并且把這個(gè)字節(jié)釋放為“使用的”,所以它可以被消費(fèi)者線程使用。 1.?????????? void?Consumer::run() ? 2.?????????? { ? 3.?????????? ????for?(int?i?=?0;?i?<?DataSize;?++i)?{ ? 4.?????????? ????????usedSpace.acquire(); ? 5.?????????? ????????cerr?<<?buffer[i?%?BufferSize]; ? 6.?????????? ????????freeSpace.release(); ? 7.?????????? ????} ? 8.?????????? ????cerr?<<?endl; ? 9.?????????? }? 在消費(fèi)者中,我們從獲取一個(gè)“使用的”字節(jié)開(kāi)始。如果緩存中沒(méi)有包含任何可讀的數(shù)據(jù),acquire()調(diào)用將會(huì)阻塞,直到生產(chǎn)者已經(jīng)產(chǎn)生一些數(shù)據(jù)。一旦我們已經(jīng)獲取了這個(gè)字節(jié),我們就打印它并且把這個(gè)字節(jié)釋放為“自由的”,使它可以被生產(chǎn)者使用來(lái)再次填充數(shù)據(jù)。 1.?????????? int?main() ? 2.?????????? { ? 3.?????????? ????Producer?producer; ? 4.?????????? ????Consumer?consumer; ? 5.?????????? ????producer.start(); ? 6.?????????? ????consumer.start(); ? 7.?????????? ????producer.wait(); ? 8.?????????? ????consumer.wait(); ? 9.?????????? ????return?0; ? 10.?????? }? main()函數(shù)的功能比較簡(jiǎn)單,負(fù)責(zé)啟動(dòng)生產(chǎn)者和消費(fèi)者線程,然后等待其各自執(zhí)行完畢后自動(dòng)退出。 QWaitCondition 對(duì)生產(chǎn)者和消費(fèi)者問(wèn)題的另一個(gè)解決方法是使用QWaitCondition,它允許線程在一定條件下喚醒其他線程。其中wakeOne()函數(shù)在條件滿足時(shí)隨機(jī)喚醒一個(gè)等待線程,而wakeAll()函數(shù)則在條件滿足時(shí)喚醒所有等待線程。
下面重寫(xiě)生產(chǎn)者和消費(fèi)者實(shí)例,以QMutex為等待條件,QWaitCondition允許一個(gè)線程在一定條件下喚醒其他線程。 1.?????????? const?int?DataSize?=?100000; ? 2.?????????? const?int?BufferSize?=?4096; ? 3.?????????? char?buffer[BufferSize]; ? 4.?????????? QWaitCondition?bufferIsNotFull; ? 5.?????????? QWaitCondition?bufferIsNotEmpty; ? 6.?????????? QMutex?mutex; ? 7.?????????? int?usedSpace?=?0;? 在緩存之外,我們聲明了兩個(gè)QWaitCondition、一個(gè)QMutex和一個(gè)存儲(chǔ)了在緩存中有多少個(gè)“使用的”字節(jié)的變量。 1.?????????? void?Producer::run() ? 2.?????????? { ? 3.?????????? ????for?(int?i?=?0;?i?<?DataSize;?++i)?{ ? 4.?????????? ????????mutex.lock(); ? 5.?????????? ????????if?(usedSpace?==?BufferSize) ? 6.?????????? ????????????bufferIsNotFull.wait(&mutex); ? 7.?????????? ????????buffer[i?%?BufferSize]?=?"MING"[uint(rand())?%?4]; ? 8.?????????? ????????++usedSpace; ? 9.?????????? ????????bufferIsNotEmpty.wakeAll(); ? 10.?????? ????????mutex.unlock(); ? 11.?????? ????} ? 12.?????? }? 在生產(chǎn)者中,我們從檢查緩存是否充滿開(kāi)始。如果是充滿的,我們等待“緩存不是充滿的”條件。當(dāng)這個(gè)條件滿足時(shí),我們向緩存寫(xiě)入一個(gè)字節(jié),增加usedSpace,并且在喚醒任何等待這個(gè)“緩存不是空白的”條件變?yōu)檎娴?strong>線程。 for循環(huán)中的所有語(yǔ)句需要使用互斥量加以保護(hù),以保護(hù)其操作的原子性。 1.?????????? bool?wait?(?QMutex?*?mutex,?unsigned?long?time?=?ULONG_MAX?);? 這個(gè)函數(shù)做下說(shuō)明,該函數(shù)將互斥量解鎖并在此等待,它有兩個(gè)參數(shù),第一個(gè)參數(shù)為一個(gè)鎖定的互斥量,第二個(gè)參數(shù)為等待時(shí)間。如果作為第一個(gè)參數(shù)的互斥量在調(diào)用是不是鎖定的或出現(xiàn)遞歸鎖定的情況,wait()函數(shù)將立即返回。 調(diào)用wait()操作的線程使得作為參數(shù)的互斥量在調(diào)用前變?yōu)殒i定狀態(tài),然后自身被阻塞變成為等待狀態(tài)直到滿足以下條件: 其他線程調(diào)用了wakeOne()或者wakeAll()函數(shù),這種情況下將返回"true"值。 第二個(gè)參數(shù)time超時(shí)(以毫秒記時(shí)),該參數(shù)默認(rèn)情況是ULONG_MAX,表示永不超時(shí),這種情況下將返回"false"值。 wait()函數(shù)返回前會(huì)將互斥量參數(shù)重新設(shè)置為鎖定狀態(tài),從而保證從鎖定狀態(tài)到等待狀態(tài)的原則性轉(zhuǎn)換。 1.?????????? void?Consumer::run() ? 2.?????????? { ? 3.?????????? ????forever?{ ? 4.?????????? ????????mutex.lock(); ? 5.?????????? ????????if?(usedSpace?==?0) ? 6.?????????? ????????????bufferIsNotEmpty.wait(&mutex); ? 7.?????????? ????????cerr?<<?buffer[i?%?BufferSize]; ? 8.?????????? ????????--usedSpace; ? 9.?????????? ????????bufferIsNotFull.wakeAll(); ? 10.?????? ????????mutex.unlock(); ? 11.?????? ????} ? 12.?????? ????cerr?<<?endl; ? 13.?????? }? 消費(fèi)者做的和生產(chǎn)者正好相反,他等待“緩存不是空白的”條件并喚醒任何等待“緩存不是充滿的”的條件的線程。 main()函數(shù)與上面的基本相同,這個(gè)不再多說(shuō)。 在QThread類的靜態(tài)函數(shù)currentThread(),可以返回當(dāng)前線程的線程ID。在X11環(huán)境下,這個(gè)ID是一個(gè)unsigned long類型的值。 小結(jié):關(guān)于?Qt?線程同步實(shí)例介紹的內(nèi)容介紹完了,希望本文對(duì)你有所幫助。
總結(jié)
以上是生活随笔為你收集整理的QT线程使用收集示例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 结合Kubernetes解读微服务的12
- 下一篇: C++:数据流和缓冲区