歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Unix知識 >> 關於Unix >> Linux下應用程序開發:QT中的多線程編程

Linux下應用程序開發:QT中的多線程編程

日期:2017/3/6 15:49:31   编辑:關於Unix
Qt 作為一種基於 C++ 的跨平台 GUI 系統,能夠提供給用戶構造圖形用戶界面的強大功能。為了滿足用戶構造復雜圖形界面系統的 需求 ,Qt 提供了豐富的多線程編程支持。 Qt 作為一種基於 C++ 的跨平台 GUI 系統,能夠提供給用戶構造圖形用戶界面的強大功能。為
  Qt 作為一種基於 C++ 的跨平台 GUI 系統,能夠提供給用戶構造圖形用戶界面的強大功能。為了滿足用戶構造復雜圖形界面系統的需求,Qt 提供了豐富的多線程編程支持。
  
  Qt 作為一種基於 C++ 的跨平台 GUI 系統,能夠提供給用戶構造圖形用戶界面的強大功能。為了滿足用戶構造復雜圖形界面系統的需求,Qt 提供了豐富的多線程編程支持。從 2.2 版本開始,Qt 主要從下面三個方面對多線程編程提供支持:一、構造了一些基本的與平台無關的線程類;二、提交用戶自定義事件的 Thread-safe 方式;三、多種線程間同步機制,如信號量,全局鎖。這些都給用戶提供了極大的方便。不過,在某些情況下,使用定時器機制能夠比利用 Qt 本身的多線程機制更方便地實現所需要的功能,同時也避免了不安全的現象發生。本文不僅對 Qt 中的多線程支持機制進行了討論,還著重探討了利用定時器機制模擬多線程編程的方法。
  
  1、系統對多線程編程的支持
  不同的平台對 Qt 的多線程支持方式是不同的。當用戶在 Windows 操作系統上安裝 Qt 系統時,線程支持是編譯器的一個選項,在 Qt 的 mkfiles 子目錄中包括了不同種類編譯器的編譯文件,其中帶有 -mt 後綴的文件才是支持多線程的。
  
  而在 Unix 操作系統中,線程的支持是通過在運行 configure 腳本文件時添加 -thread 選項加入的。安裝過程將創建一個獨立的庫,即 libqt-mt,因此要支持多線程編程時,必須與該庫鏈接(鏈接選項為-lqt-mt),而不是與通常的 Qt 庫(-lqt)鏈接。
  
  另外,無論是何種平台,在增加線程支持時都需要定義宏 QT_THREAD_SUPPORT(即增加編譯選項-DQT_THREAD_SUPPORT)。在 Windows 操作系統中,這一點通常是在 qconfig.h 文件中增加一個選項來實現的。而在 Unix 系統中通常添加在有關的 Makefile 文件中。
  
  2、Qt中的線程類
  在 Qt 系統中與線程相關的最重要的類當然是 QThread 類,該類提供了創建一個新線程以及控制線程運行的各種方法。線程是通過 QThread::run() 重載函數開始執行的,這一點很象 Java 語言中的線程類。在 Qt 系統中,始終運行著一個GUI 主事件線程,這個主線程從窗口系統中獲取事件,並將它們分發到各個組件去處理。在 QThread 類中還有一種從非主事件線程中將事件提交給一個對象的方法,也就是 QThread::postEvent()方法,該方法提供了 Qt 中的一種 Thread-safe 的事件提交過程。提交的事件被放進一個隊列中,然後 GUI 主事件線程被喚醒並將此事件發給相應的對象,這個過程與一般的窗口系統事件處理過程是一樣的。值得注意的是,當事件處理過程被調用時,是在主事件線程中被調用的,而不是在調用QThread::postEvent 方法的線程中被調用。比如用戶可以從一個線程中迫使另一個線程重畫指定區域:
  
  QWidget *mywidget;
  QThread::postEvent(mywidget, new QPaintEvent(QRect(0,0,100,100)));
  
  然而,只有一個線程類是不夠的,為編寫出支持多線程的程序,還需要實現兩個不同的線程對共有數據的互斥訪問,因此 Qt 還提供了 QMutex 類,一個線程在訪問臨界數據時,需要加鎖,此時其他線程是無法對該臨界數據同時加鎖的,直到前一個線程釋放該臨界數據。通過這種方式才能實現對臨界數據的原子操作。
  
  除此之外,還需要一些機制使得處於等待狀態的線程在特定情況下被喚醒。QWaitCondition 類就提供了這種功能。當發生特定事件時,QWaitCondition 將喚醒等待該事件的所有線程或者喚醒任意一個被選中的線程。
  
  3、用戶自定義事件在多線程編程中的應用
  在 Qt 系統中,定義了很多種類的事件,如定時器事件、鼠標移動事件、鍵盤事件、窗口控件事件等。通常,事件都來自底層的窗口系統,Qt 的主事件循環函數從系統的事件隊列中獲取這些事件,並將它們轉換為 QEvent,然後傳給相應的 QObjects 對象。
  
  除此之外,為了滿足用戶的需求,Qt 系統還提供了一個 QCustomEvent 類,用於用戶自定義事件,這些自定義事件可以利用 QThread::postEvent() 或者QApplication::postEvent() 被發給各種控件或其他 QObject 實例,而 QWidget 類的子類可以通過 QWidget::customEvent() 事件處理函數方便地接收到這些自定義的事件。需要注意的是:QCustomEvent 對象在創建時都帶有一個類型標識 id 以定義事件類型,為了避免與 Qt 系統定義的事件類型沖突,該 id 值應該大於枚舉類型 QEvent::Type 中給出的 "User" 值。
  
  在下面的例子中,顯示了多線程編程中如何利用用戶自定義事件類。
  
  UserEvent類是用戶自定義的事件類,其事件標識為346798,顯然不會與系統定義的事件類型沖突。
  
  class UserEvent : public QCustomEvent  //用戶自定義的事件類
  {
  public:
  UserEvent(QString s) : QCustomEvent(346798), sz(s) { ; }
  QString str() const { return sz; }
  private:
  QString sz;  
  };
  
  UserThread類是由QThread類繼承而來的子類,在該類中除了定義有關的變量和線程控制函數外,最主要的是定義線程的啟動函數UserThread::run(),在該函數中創建了一個用戶自定義事件UserEvent,並利用QThread類的postEvent函數提交該事件給相應的接收對象。
  
  class UserThread : public QThread   //用戶定義的線程類
  {
  public:
  UserThread(QObject *r, QMutex *m, QWaitCondition *c);
  QObject *receiver;
  }
  
  void UserThread::run()   //線程類啟動函數,在該函數中創建了一個用戶自定義事件
  {UserEvent *re = new UserEvent(resultstring);
   QThread::postEvent(receiver, re);
  }
  
  UserWidget類是用戶定義的用於接收自定義事件的QWidget類的子類,該類利用slotGo()函數創建了一個新的線程recv(UserThread類),當收到相應的自定義事件(即id為346798)時,利用customEvent函數對事件進行處理。
  
  void UserWidget::slotGo()  //用戶定義控件的成員函數
  {mutex.lock(); 
  if (! recv)
  recv = new UserThread(this, &mutex, &condition);
  recv->start();
  mutex.unlock();
  }
  
  void UserWidget::customEvent(QCustomEvent *e)  //用戶自定義事件處理函數
  {if (e->type()==346798)
  {
  UserEvent *re = (UserEvent *) e;
      newstring = re->str();
    }
  }
  
  在這個例子中,UserWidget對象中創建了新的線程UserThread,用戶可以利用這個線程實現一些周期性的處理(如接收底層發來的消息等),一旦滿足特定條件就提交一個用戶自定義的事件,當UserWidget對象收到該事件時,可以按需求做出相應的處理,而一般情況下,UserWidget對象可以正常地執行某些例行處理,而完全不受底層消息的影響。
  
  4、利用定時器機制實現多線程編程
  為了避免Qt系統中多線程編程帶來的問題,還可以使用系統中提供的定時器機制來實現類似的功能。定時器機制將並發的事件串行化,簡化了對並發事件的處理,從而避免了thread-safe方面問題的出現。
  
  在下面的例子中,同時有若干個對象需要接收底層發來的消息(可以通過Socket、FIFO等進程間通信機制),而消息是隨機收到的,需要有一個GUI主線程專門負責接收消息。當收到消息時主線程初始化相應對象使之開始處理,同時返回,這樣主線程就可以始終更新界面顯示並接收外界發來的消息,達到同時對多個對象的控制;另一方面,各個對象在處理完消息後需要通知GUI主線程。對於這個問題,可以利用第3節中的用戶自定義事件的方法,在主線程中安裝一個事件過濾器,來捕捉從各個對象中發來的自定義事件,然後發出信號調用主線程中的一個槽函數。
  
  另外,也可以利用Qt中的定時器機制實現類似的功能,而又不必擔心Thread-safe問題。下面就是有關的代碼部分:
  
  在用戶定義的Server類中創建和啟動了定時器,並利用connect函數將定時器超時與讀取設備文件數據相關聯:
  
  Server:: Server(QWidget *parent) : QWidget(parent)
  {
  readTimer = new QTimer(this);  //創建並啟動定時器
    connect(readTimer, SIGNAL(timeout()), this, SLOT(slotReadFile()));  //每當定時器超時時調用函數slotReadFile讀取文件
    readTimer->start(100);
  }
  
  slotReadFile函數負責在定時器超時時,從文件中讀取數據,然後重新啟動定時器:
  
  int Server::slotReadFile()  // 消息讀取和處理函數
  {
   readTimer->stop();   //暫時停止定時器計時
   ret = read(file, buf );  //讀取文件
  if(ret == NULL)
  {  readTimer->start(100);   //當沒有新消息時,重新啟動定時器
    return(-1);
  }
   else
      根據buf中的內容將消息分發給各個相應的對象處理……;
  readTimer->start(100);  //重新啟動定時器
  }
  
  在該程序中,利用了類似輪循的方式定時對用戶指定的設備文件進行讀取,根據讀到的數據內容將信息發送到各個相應的對象。用戶可以在自己的GUI主線程中創建一個Server類,幫助實現底層的消息接收過程,而本身仍然可以處理諸如界面顯示的問題。當各個對象完成處理後,通過重新啟動定時器繼續進行周期性讀取底層設備文件的過程。當然,這種方法適合於各對象對事件的處理時間較短,而底層設備發來消息的頻率又相對較慢的情況。在這種情況下,上述方法完全可以滿足用戶的需求,而又避免了處理一些與線程並發有關的復雜問題。
  
  當然,利用定時器機制實現多線程編程在某些方面具有一定的局限性,有關到底如何實現多線程編程,如何編寫出效率更高的代碼,還有待於開發者進一步研究和探討。

Copyright © Linux教程網 All Rights Reserved