Qt 線程基礎(QThread、QtConcurrent、QThreadPool等)

使用線程

基本上有種使用線程的場合: html

  • 經過利用處理器的多個核使處理速度更快。
  • 爲保持GUI線程或其餘高實時性線程的響應,將耗時的操做或阻塞的調用移到其餘線程。

什麼時候使用其餘技術替代線程

開發人員使用線程時須要很是當心。啓動線程是很容易的,但確保全部共享數據保持一致很難。遇到問題每每很難解決,這是因爲在一段時間內它可能只出現一次或只在特定的硬件配置下出現。在建立線程來解決某些問題以前,應該考慮一些替代的技術 編程

替代技術 安全

註解 網絡

QEventLoop::processEvents() 多線程

在一個耗時的計算操做中反覆調用QEventLoop::processEvents() 能夠防止界面的假死。儘管如此,這個方案可伸縮性並不太好,由於該函數可能會被調用地過於頻繁或者不夠頻繁。 併發

QTimer app

後臺處理操做有時能夠方便地使用Timer安排在一個在將來的某一時刻執行的槽中來完成。在沒有其餘事件須要處理時,時間隔爲0的定時器超時事件被相應 框架

QSocketNotifier 
QNetworkAccessManager 
QIODevice::readyRead() 異步

這是一個替代技術,替代有一個或多個線程在慢速網絡執行阻塞讀的狀況。只要響應部分的計算能夠快速執行,這種設計比在線程中實現的同步等待更好。與線程相比這種設計更不容易出錯且更節能(energy efficient)。在許多狀況下也有性能優點。 函數式編程

通常狀況下,建議只使用安全和通過測試的方案而避免引入特設線程的概念。QtConcurrent 提供了一個將任務分發處處理器全部的核的易用接口。線程代碼徹底被隱藏在 QtConcurrent 框架下,因此你沒必要考慮細節。儘管如此,QtConcurrent 不能用於線程運行時須要通訊的狀況,並且它也不該該被用來處理阻塞操做。

應該使用 Qt 線程的哪一種技術?

有時候,你須要的不只僅是在另外一線程的上下文中運行一個函數。您可能須要有一個生存在另外一個線程中的對象來爲 GUI線程提供服務。也許你想在另外一個始終運行的線程中來輪詢硬件端口並在有關注的事情發生時發送信號到GUI線程。Qt爲開發多線程應用程序提供了多種 不一樣的解決方案。解決方案的選擇依賴於新線程的目的以及線程的生命週期。

生命週期

開發任務

解決方案

一次調用

在另外一個線程中運行一個函數,函數完成時退出線程

編寫函數,使用QtConcurrent::run 運行它

派生QRunnable,使用QThreadPool::globalInstance()->start()運行它

派生QThread,從新實現QThread::run() ,使用QThread::start()運行它

一次調用

須要操做一個容器中全部的項。使用處理器全部可用的核心。一個常見的例子是從圖像列表生成縮略圖。

QtConcurrent 提供了map()函你數來將操做應用到容器中的每個元素,提供了fitler()函數來選擇容器元素,以及指定reduce函數做爲選項來組合剩餘元素。

一次調用

一個耗時運行的操做須要放入另外一個線程。在處理過程當中,狀態信息須要發送會GUI線程。

使用QThread,從新實現run函數並根據須要發送信號。使用信號槽的queued鏈接方式將信號鏈接到GUI線程的槽函數。

持久運行

生存在另外一個線程中的對象,根據要求須要執行不一樣的任務。這意味着工做線程須要雙向的通信。

派生一個QObject對象並實現須要的信號和槽,將對象移動到一個運行有事件循環的線程中並經過queued方式鏈接的信號槽進行通信。

持久運行

生存在另外一個線程中的對象,執行諸如輪詢端口等重複的任務並與GUI線程通信。

同上,可是在工做線程中使用一個定時器來輪詢。儘管如此,處理輪詢的最好的解決方案是完全避免它。有時QSocketNotifer是一個替代。

Qt線程基礎

QThread是一個很是便利的跨平臺的對平臺原生線程的抽象。啓動一個線程是很簡單的。讓咱們看一個簡短的代碼:生成一個在線程內輸出"hello"並退出的線程。

// hellothread/hellothread.h  class HelloThread : public QThread  {      Q_OBJECT  private:      void run();  };

咱們從QThread派生出一個類,並從新實現run方法。

// hellothread/hellothread.cpp  void HelloThread::run()  {       qDebug() << "hello from worker thread " << thread()->currentThreadId();  }

run方法中包含將在另外一個線程中運行的代碼。在本例中,一個包含線程ID的消息被打印出來。  QThread::start() 將在另外一個線程中被調用。

int main(int argc, char *argv[])  {      QCoreApplication app(argc, argv);      HelloThread thread;      thread.start();      qDebug() << "hello from GUI thread " << app.thread()->currentThreadId();      thread.wait();  // do not exit before the thread is completed!      return 0;  }

QObject與線程

QObject有線程關聯(thread affinity)[如何翻譯?關聯?依附性?dbzhang800 20110618],換句話說,它生存於一個特定的線程。這意味着,在建立時QObject保存了到當前線程的指針。當事件使用postEvent()被 派發時,這個信息變得頗有用。事件被放置到相應線程的事件循環中。若是QObject所依附的線程沒有事件循環,該事件將永遠不會被傳遞。

要啓動事件循環,必須在run()內調用exec()。線程關聯能夠經過moveToThread()來更改。

如上所述,當從其餘線程調用對象的方法時開發人員必須始終保持謹慎。線程關聯不會改變這種情況。 Qt文檔中將一些方法標記爲線程安全。postEvent()就是一個值得注意的例子。一個線程安全的方法能夠同時在不一樣的線程被調用。

一般狀況下並不會併發訪問的一些方法,在其餘線程調用對象的非線程安全的方法在出現形成意想不到行爲的併發訪問前 數千次的訪問可能都是工做正常的。編寫測試代碼不能徹底確保線程的正確性,但它仍然是重要的。在Linux上,Valgrind和Helgrind有助於 檢測線程錯誤。

QThread的內部結構很是有趣:

  • QThread並不生存於執行run()的新線程內。它生存於舊線程中。
  • QThread的大多數成員方法是線程的控制接口,並設計成從舊線程中被調用。不要使用moveToThread()將該接口移動到新建立的線程中;調用moveToThread(this)被視爲很差的實踐。
  • exec()和靜態方法usleep()、msleep()、sleep()要在新建立的線程中調用。
  • QThread子類中定義的其餘成員可在兩個線程中訪問。開發人員負責訪問的控制。一個典型的策略是在start()被調用前設置成員變量。一旦 工做線程開始運行,主線程不該該操做其餘成員。當工做線程終止後,主線程能夠再次訪問其餘成員。這是一個在線程開始前傳遞參數並在結束後收集結果的便捷的 策略。

QObject必須始終和parent在同一個線程。對於在run()中生成的對象這兒有一個驚人的後果:

void HelloThread::run()  {       QObject *object1 = new QObject(this);  //error, parent must be in the same thread       QObject object2;  // OK       QSharedPointer <QObject> object3(new QObject); // OK  }

使用互斥量保護數據的完整

互斥量是一個擁有lock()和unlock()方法並記住它是否已被鎖定的對象。互斥量被設計爲從多個線程調 用。若是信號量未被鎖定lock()將當即返回。下一次從另外一個線程調用會發現該信號量處於鎖定狀態,而後lock()會阻塞線程直到其餘線程調用 unlock()。此功能能夠確保代碼段將在同一時間只能由一個線程執行。

使用事件循環防止數據破壞

Qt的事件循環對線程間的通訊是一個很是有價值的工具。每一個線程均可以有它本身的事件循環。在另外一個線程中調用一個槽的一個安全的方法是將調用放置到另外一個線程的事件循環中。這能夠確保目標對象調用另外一個的成員函數以前能夠完成當前正在運行的成員函數。

那麼,如何才能把一個成員調用放於一個事件循環中? Qt的有兩種方法來作這個。一種方法是經過queued信號槽鏈接;另外一種是使用QCoreApplication::postEvent()派發一個事 件。queued的信號槽鏈接是異步執行的信號槽鏈接。內部實現是基於posted的事件。信號的參數放入事件循環後信號函數的調用將當即返回。

鏈接的槽函數什麼時候被執行依賴於事件循環其餘的其餘操做。

經過事件循環通訊消除了咱們使用互斥量時所面臨的死鎖問題。這就是咱們爲何推薦使用事件循環,而不是使用互斥量鎖定對象的緣由。

處理異步執行

一種得到一個工做線程的結果的方法是等待線程終止。在許多狀況下,一個阻塞等待是不可接受的。阻塞等待的替代方法 是異步的結果經過posted事件或者queued信號槽進行傳遞。因爲操做的結果不會出如今源代碼的下一行而是在位於源文件其餘部分的一個槽中,這會產 生必定的開銷,由於,但在位於源文件中其餘地方的槽。 Qt開發人員習慣於使用這種異步行爲工做,由於它很是類似於GUI程序中使用的的事件驅動編程。


***************************************************************************************************


1、QThreadPool

  QThreadPool管理一組線程。它負責管理和回收單個QThread對象以減小程序中線程建立的開銷。每一個Qt應用程序都有一個全局的QThreadPool對象,可經過方法globalInstance()得到。爲了調用QThreadPool中的一個線程,須要提供一個從QRunnable繼承過來的類,並實現其中的run方法。而後建立一個該類的對象,傳遞給QThreadPool::start()方法。代碼片段以下:

[cpp]  view plain copy
  1. class HelloWorldTask : public QRunnable  
  2.  {  
  3.      void run()  
  4.      {  
  5.          qDebug() << "Hello world from thread" << QThread::currentThread();  
  6.      }  
  7.  }  
  8.   
  9.  HelloWorldTask *hello = new HelloWorldTask();  
  10.  // QThreadPool takes ownership and deletes 'hello' automatically  
  11.  QThreadPool::globalInstance()->start(hello);  

默認狀況下, QThreadPool自動刪除QRunnable對象。使用QRunnable::setAutoDelete()方法能夠改變該默認行爲。QThreadPool支持在QRunnable::run方法中經過調用tryStart(this)來屢次執行相同的QRunnable。當最後一個線程退出run函數後,若是autoDelete啓用的話,將刪除QRunnable對象。在autoDelete啓用的狀況下,調用start()方法屢次執行同一QRunnable會產生競態,就避免這樣作。

  那些在必定時間內會使用的線程將會過時。默認的過時時間是30秒。可經過setExpiryTimeout()方法來設置。設置一個負數的超時值表明禁用超時機制。方法maxThreadCount()能夠查詢可以使用的最大線程數,你也能夠設置最大的線程數。activeThreadCount反應的是當前正在被使用中的線程數個數。reserveThread函數保留某個線程爲外部使用,releaseThread釋放該線程,這樣就能夠被再次使用。

2、QtConcurrent命名空間

QtConcurrent命名空間裏提供了一些高級API,利用這些API能夠編寫多線程程序,而不用直接使用比較低級的一些類,如mutext,lock, waitcondition以及semaphore等。使用QtConcurrent命令空間的API編寫的程序會根據處理器的數目自動地調整線程的個數。QtConcurrent包含了用於並行列表處理的函數式編程,包含實現共享內存系統的MapReduceFilterReduce, 以及管理GUI應用程序中異步計算的類。相關的類說明以下:

QtConcurrent::map()

appliesa function to every item in a container, modifying the itemsin-place

QtConcurrent::mapped()

islike map(), except that it returns a new container with themodifications

QtConcurrent::mappedReduced()

islike mapped(), except that the modified results are reduced orfolded into a single result.

QtConcurrent::filter()

litems from a container based on the result of a filter function.

QtConcurrent::filtered()

islike filter(), except that it returns a new container with thefiltered results

QtConcurrent::filteredReduced()

islike filtered(), except that the filtered results are reduced orfolded into a single result

QtConcurrent::run() 

runsa function in another thread.

QFutureIterator

allowsiterating through results available via QFuture.

QFutureWatcher

allowsmonitoring a QFuture usingsignals-and-slots.

QFutureSynchronizer

isa convenience class that automatically synchronizes severalQFutures.

代碼實例:

[cpp]  view plain copy
  1. using namespace QtConcurrent;  
  2. void hello(QString name)  
  3. {  
  4.     qDebug() << "Hello" << name << "from" << QThread::currentThread();  
  5. }  
  6. int main(int argc, char **argv)  
  7. {  
  8.     QApplication app(argc, argv);  
  9.     QFuture<void> f1 = run(hello, QString("Alice"));  
  10.     QFuture<void> f2 = run(hello, QString("Bob"));  
  11.     f1.waitForFinished();  
  12.     f2.waitForFinished();  
  13.     return app.exec();  

**********************************************************************************************************



QtConcurrent::run() 的使用

2011-11-25 10:36  155人閱讀  評論(0)  收藏  舉報

QFuture<T> run(const Class *object, T (Class::*fn)(Param1, Param2, Param3, Param4, Param5) const, const Arg1 &arg1,const Arg2 &arg2, const Arg3 &arg3, const Arg4 &arg4, const Arg5 &arg5)

run()函數的原型如上,此函數是QtConcurrent命名空間裏的函數.主要功能是令啓動一個線程來執行一個函數.Concurrent的英文示意就是併發的意思.

下面簡要的介紹run()函數的使用方法:

1.首先要有一個須要在另外開啓的線程中執行的函數:

void thread_add(QObject *receiver,int a,int b)

{
 QString message=QString("%1 + %2 = %3").arg(a).arg(b).arg(a+b);
 QApplication::postEvent(receiver,new ProgressEvent(true, message));
}

函數在線程中運行完畢後會向receiver發送一個消息,來返回結果.

 

2.有了要在線程中運行的函數,再來看看怎麼啓動線程來運行這個函數

void MainWindow::on_pushButton_clicked()

{
for(int i=0;i<9;i++)
 for(int j=0;j<9;j++)
 QtConcurrent::run(thread_add,this,i,j);
}

點擊一個按鈕就會運行這段代碼,而後啓動8*8=64個線程,線程要運行的函數就是thread_add(以前定義的),消息接收對象就是MainWindow這個類的實例

 

3.線程獲得了運行會發送消息給MainWindow,MainWindow從新實現bool MainWindow::event ( QEvent * event )處理接收到的消息,並顯示出來

bool MainWindow::event ( QEvent * event )

{
 if (event->type() ==
 static_cast<QEvent::Type>(ProgressEvent::EventId)) {
 ProgressEvent *progressEvent =
 static_cast<ProgressEvent*>(event);
 Q_ASSERT(progressEvent);
 ui->teLog->append(progressEvent->message);
 return true;
 }
 return QMainWindow::event(event);
}

 

再給出自定義的消息結構

struct ProgressEvent : public QEvent

{
 enum {EventId = QEvent::User};
 explicit ProgressEvent(bool saved_, const QString &message_)
 : QEvent(static_cast<Type>(EventId)),
 saved(saved_), message(message_) {}
 const bool saved;
 const QString message;
};
相關文章
相關標籤/搜索