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