QT核心編程之Qt線程 (3)
QT核心編程之Qt線程是本節(jié)要介紹的內(nèi)容,QT核心編程我們要分幾個(gè)部分來介紹,想?yún)⒖几鄡?nèi)容,請(qǐng)看末尾的編輯推薦進(jìn)行詳細(xì)閱讀,先來看本篇內(nèi)容。
Qt對(duì)線程提供了支持,它引入了一些基本與平臺(tái)無(wú)關(guān)的線程類、線程安全傳遞事件的方式和全局Qt庫(kù)互斥量允許你從不同的線程調(diào)用Qt的方法。Qt中與線程應(yīng)用相關(guān)的類如表6所示。
表6 Qt中與線程相關(guān)的類
使用線程需要Qt提供相應(yīng)的線程庫(kù)的支持,因此,在編譯安裝Qt時(shí),需要加上線程支持選項(xiàng)。
當(dāng)在Windows操作系統(tǒng)上編譯Qt時(shí),線程支持是在一些編譯器上的一個(gè)選項(xiàng)。在Windows操作系統(tǒng)上編譯應(yīng)用程序時(shí),通過在qconfig.h文件中增加一個(gè)選項(xiàng)來解決來解決這個(gè)問題。
在Mac OS X和Unix上編譯Qt時(shí),你應(yīng)在運(yùn)行configure腳本時(shí)添加-thread選項(xiàng)。在Unix平臺(tái)上,多線程程序必須用特殊的線程支持庫(kù)連接,多線程程序必須連接線程支持庫(kù)libqt-mt,而不是標(biāo)準(zhǔn)的Qt庫(kù)。編譯應(yīng)用程序時(shí),你應(yīng)該使用宏定義QT_THREAD_SUPPORT來編譯(如:編譯時(shí)使用-DQT_THREAD_SUPPORT)。
1、線程類QThread
在 Qt中提供了QThread線程類,它提供了創(chuàng)建一個(gè)新線程的方法。線程通過重載 QThread::run()函數(shù)開始執(zhí)行的,這一點(diǎn)與Java中的線程類相似。
示例1:一個(gè)簡(jiǎn)單的線程
下面的例子實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的繼承自QThread的用戶線程類,并運(yùn)行2個(gè)線程,線程b在線程a運(yùn)行完后運(yùn)行。代碼列出如下:
- class MyThread : public QThread {
- public: virtual void run();
- };
- void MyThread::run() //運(yùn)行線程{
- for( int count = 0;
- count < 20; count++ ) {
- sleep( 1 );
- qDebug( "Ping!" );
- }
- }
- int main(){
- MyThread a;
- MyThread b;
- a.start(); //通過調(diào)用run()函數(shù)來執(zhí)行
- b.start();
- a.wait();
- b.wait();
- }
只有一個(gè)線程類是不夠的,對(duì)于支持多線程的程序來說,還需要保護(hù)兩個(gè)不同的線程對(duì)數(shù)據(jù)的同時(shí)訪問,因此 Qt 提供了QMutex 類,一個(gè)線程可以鎖住互斥量,當(dāng)互斥量被鎖住時(shí),將阻塞其它線程訪問臨界數(shù)據(jù),直到這個(gè)線程釋放互斥量。這樣,可以保護(hù)臨界數(shù)據(jù)一次只能被一個(gè)線程訪問。
Qt庫(kù)互斥量(qApp->lock()和qApp->unlock())是在訪問Qt的GUI界面資源時(shí)用到的互斥量。在 Qt中沒有使用互斥量而調(diào)用一個(gè)函數(shù)通常會(huì)導(dǎo)致不可預(yù)知的行為。從另外一個(gè)線程中調(diào)用Qt的一個(gè)GUI相關(guān)函數(shù)需要使用Qt庫(kù)互斥量。在這種情況下,所有訪問圖形或窗口系統(tǒng)資源的函數(shù)都與GUI相關(guān)。如果對(duì)象僅被一個(gè)線程訪問,使用容器類,字符串或者輸入/輸出類不需要任何互斥量。
2、線程安全的事件傳遞
在Qt中,一個(gè)線程總是一個(gè)事件線程,線程從窗口系統(tǒng)中拉出事件并且把它們分發(fā)給窗口部件。靜態(tài)方法QThread::postEvent從線程中郵遞事件,而不是從事件線程。事件線程被喚醒并且事件象一個(gè)正常窗口系統(tǒng)的事件一樣在事件線程中被分發(fā)。例如,你可以從不同的線程強(qiáng)制一個(gè)窗口部件進(jìn)行重繪,方法如下:
- QWidget *mywidget;QThread::postEvent( mywidget, new QPaintEvent( QRect(0, 0, 100, 100) ) );
上述代碼將異步地使mywidget在它區(qū)域中重繪一塊100*100的正方形區(qū)域。
另外,還需要一些機(jī)制使得處于等待狀態(tài)的線程在給定條件下被喚醒。QWaitCondition 類就提供了這種功能。線程等待的條件QWaitCondition滿足,QWaitCondition表明發(fā)生了什么事情,它阻塞直到這件事情發(fā)生。當(dāng)發(fā)生給定的事情時(shí),QWaitCondition 將喚醒等待該事情的所有線程或者喚醒任意一個(gè)被選中的線程。(這和POSIX線程條件變量具有相同功能,是Unix上的一種實(shí)現(xiàn)。)
示例2:QWaitCondition類應(yīng)用
下面這個(gè)例子的功能是:當(dāng)你按下按鈕,這個(gè)程序就會(huì)喚醒worker線程,這個(gè)線程在按鈕上顯示工作狀態(tài):等待(Waiting)還是正在工作(Working)。當(dāng)按鈕被按下時(shí),worker線程正在工作,那么對(duì)線程不產(chǎn)生影響。當(dāng)run函數(shù)再次循環(huán)到mycond.wait()時(shí),線程阻塞并等待。當(dāng)按鈕再被按下時(shí),觸發(fā)slotClicked()函數(shù)運(yùn)行,喚醒等待的線程。
- #include <qapplication.h>
- #include <qpushbutton.h> // 全局條件變量
- QWaitCondition mycond; // Worker類實(shí)現(xiàn)
- class Worker : public QPushButton, public QThread{
- Q_OBJECT public: Worker(QWidget *parent = 0, const char *name = 0) : QPushButton(parent, name) {
- setText("Start Working"); // 將QPushButton繼承來的信號(hào)與槽slotClicked()連接起來
- connect(this, SIGNAL(clicked()), SLOT(slotClicked())); // 調(diào)用從QThread繼承來的start()方法開始線程的執(zhí)行
- QThread::start();
- }
- public slots: void slotClicked() { // 喚醒等待這個(gè)條件變量的一個(gè)線程
- mycond.wakeOne();
- }
- protected: void run() //重載run函數(shù) {
- while ( TRUE ) { // 鎖定應(yīng)用程序互斥鎖,并且設(shè)置窗口標(biāo)題來表明我們正在等待開始工作
- qApp->lock();
- setCaption( "Waiting" );
- qApp->unlock(); // 等待直到我們被告知可以繼續(xù)
- mycond.wait(); // 如果到了這里,表示我們已經(jīng)被另一個(gè)線程喚醒
- qApp->lock();
- setCaption( "Working!" );// 設(shè)置標(biāo)題,表示正在工作
- qApp->unlock();
- }
- }
- };
- int main( int argc, char **argv ){
- QApplication app( argc, argv ); // 創(chuàng)建一個(gè)worker
- Worker firstworker( 0, "worker" );
- app.setMainWidget( &worker ); //將worker設(shè)置為應(yīng)用程序的主窗口
- worker.show();
- return app.exec();
- }
當(dāng)進(jìn)行線程編程時(shí),需要注意的一些事項(xiàng):
(1)在持有Qt庫(kù)互斥量時(shí)不要做任何阻塞操作。這將凍結(jié)事件循環(huán)。
(2)確認(rèn)你鎖定一個(gè)遞歸QMutex的次數(shù)等于解鎖的次數(shù),不能多也不能少。
(3)在調(diào)用除了Qt容器和工具類外的任何東西之前鎖定Qt應(yīng)用程序互斥量。
(4)謹(jǐn)防隱含的共享類,如果你需要在線程之間指定它們,你應(yīng)該用detach()分離它們。
(5)小心沒有被設(shè)計(jì)成線程安全的Qt類,例如,QPtrList的API接口不是線程安全的,并且如果不同的線程需要遍歷一個(gè)QPtrList,它們應(yīng)該在調(diào)用QPtrList::first()之前鎖住,在到達(dá)終點(diǎn)后解鎖。
(6)確信僅在GUI線程中創(chuàng)建繼承自QWidget、QTimer和QSocketNotifier的對(duì)象。在一些平臺(tái)上,創(chuàng)建在線程中而不是GUI線程的對(duì)象永遠(yuǎn)不會(huì)接收到底層窗口系統(tǒng)的事件。
(7)和上面很相似,只在GUI線程中使用QNetwork類。因?yàn)樗械腝Network類都是異步的,沒必要把QSocket用在多線程中。
(8)永遠(yuǎn)不要嘗試在不是GUI線程的線程中調(diào)用processEvents()函數(shù)。這也包括QDialog::exec()、QPopupMenu::exec()、QApplication::processEvents()和其它一些函數(shù)。
(9)在你的應(yīng)用程序中,不要把普通的Qt庫(kù)和支持線程的Qt庫(kù)混合使用。這意味著如果你的程序使用了支持線程的Qt庫(kù),你就不能連接普通的Qt庫(kù)、動(dòng)態(tài)的載入普通Qt庫(kù)或者動(dòng)態(tài)地連接其它依賴普通Qt庫(kù)的庫(kù)或者插件。在一些系統(tǒng)上,這樣做會(huì)導(dǎo)致Qt庫(kù)中使用的靜態(tài)數(shù)據(jù)崩潰。
小結(jié):QT核心編程之Qt線程 的內(nèi)容介紹完了,希望本節(jié)內(nèi)容隨你有所幫助,如果需要更多資料請(qǐng)參考編輯推薦。
【編輯推薦】