在QtCreator當中用到了很多的Concurrent(併發),好比編譯時,搜索時等。其實在不少場合中都須要用到,通常是CPU去作一項大任務(花費較長時間)時相應用戶操做。另外一個重要用途就是在當前這個多核,甚至多CPU的年代,並行變成成爲一種時尚了,它也確實提升了應用程序的性能。個人電腦是單CPU,2核心4線程,因此相比單應用程序,應該能夠將性能提升將近4倍(固然不會是4倍的)。我所聽過的有不少庫是這方面的,好比CUDA,OpenCL,OpenMP。Qt是怎麼作的還真不知道,望高手指教。首先來測試下:編程
#include <QtCore/QCoreApplication> #include <QtConcurrentRun> #include <QtConcurrentMap> #include <qmath.h> #include <QFuture> #include <QVector> #include <QTime> #include <QObject> #include <QFutureWatcher> #include "myslot.h" QTime t; void test(const int &N) { int k = N * 100; int v = 0; for(int i = 0; i < k; ++i) ++v; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); MySlot ms; QVector<int> datas(1000, 12343); t.start(); for(int i = 0; i < 1000; ++i) { test(datas[i]); } printf("%d ms\n", t.elapsed()); t.restart(); QFutureWatcher<void> *pW = new QFutureWatcher<void>; QObject::connect(pW, SIGNAL(finished()), &ms, SLOT(onFinish())); QFuture<void> future = QtConcurrent::map(datas, test); pW->setFuture(future); return a.exec(); }
這裏的MySlot裏面就有一個onFinish()而已,完成時間輸出,雖然QTime計時並不許確,可是這裏已經能夠看出問題了。windows
提升了將近三倍的速度。多線程
這裏分爲三個部分來談Concurrent在Qt中的用法(QThread在這裏就不談了,有點low-level,靈活性雖強,但很差控制,須要較好的多線程經驗)。併發
QtConcurrent::run()使用Qt的全局線程池,找到一個空閒線程執行函數。在操做系統中,是有流水線技術的,那麼IO操做與CPU運算是能夠同時進行的,若是咱們的操做中有這兩種操做,就可使用QtConcurrent併發完成。看下面的函數原型:app
第一個是函數名,接下來是不定長的參數列表。每一個線程就去執行這個函數,直到完成任務線程歸還給線程池。這裏就有一個問題,假如我如今有不少任務,那麼,須要多少個線程來作這些任務呢?固然,並非開闢的線程越多越好的,開闢過多的線程不只浪費資源,同時也會使CPU因切換時間片而性能降低。爲了減小線程數量,我能夠將任務分紅N組,這個N的取值能夠根據具體的計算機的CPU來肯定,個人是4核心,可使用4個線程4個分組(這個不是最好的,由於沒有考慮到IO與CPU併發的問題)。Qt中有一個函數能夠返回一個較合適的CPU線程數量QThread::idealThreadCount(),他返回的就是CPU的核心數量。
若是但願在每一個組裏根據當前處理狀態來通知消息,可使用QApplication::postEvent(), QApplication :: sendEvent()或者QMetaObject::InvokeMethod(),還有signal-slot,他們各有各自的好處。ide
postEvent()相似與windows中的PostMessage(),使用非阻塞的方式發送消息到消息隊列當中,相反SendMessage()就是阻塞的方式了,知道消息被處理以後纔會返回。可是Qt中的sendEvent()有些特別,若是在別的線程sendEvent(),你會獲得這樣的ASSERT:
ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread c33f640. Receiver '' (of type 'Widget') was created in thread 277818"。
在Qt高級編程中有這樣的一些話,「sendEvent()方法馬上分派消息--可是應該當心使用,或者就不使用,好比在多線程編程中使用sendEvent()會使事件句柄歸發送者線程擁有,而不是接收者線程。而且沒有事件壓縮或者重排序,sendEvent()不會刪除事件,所以應該在棧上建立事件。」
因此仍是用postEvent比較好,具體用法參考Qt Assistant。函數
invokeMethod()的函數原型很是的長,以下 post
QString retVal; QMetaObject::invokeMethod(obj, "compute", Qt::DirectConnection, Q_RETURN_ARG(QString, retVal), Q_ARG(QString, "sqrt"), Q_ARG(int, 42), Q_ARG(double, 9.7));
第三個參數是連接類型,在多線程中Qt::QueuedConnection是常用的。Q_RETURN_ARG只有在使用Qt::DirectConnection纔會有效。性能
使用QRunnable就須要派生一個子類了,而後重寫虛函數run()。在run函數中完成要作的任務,可是QRunnable不是QObject的子類,那麼只能用invoke方法了和自定義事件(custom event)。另外在run結束以後,線程池將會刪除這個runnable對象,那麼在構造函數裏使用setAutoDelete(false)能夠將控制權轉交給用戶。建立任務時,使用的方法是QThreadPool::globalInstance()->start(MyRunnableObj);測試
使用QtConcurrent::run()方法能夠返回一個QFuture<T>, 咱們可使用QFutureWatcher<T>來跟蹤處理的過程,而使用QRunnable類就須要咱們本身管理了。
有四種方式來監視任務進度,filter,mapper,reducer,function。最後一種是沒法跟蹤具體進度的,只能監視到任務開始與任務結束。這裏分別說一下前三者的用法。
給定一個集合如QList,QVector等,經過一個過濾filter函數的返回值返回一個新的集合;mapper返回新類型的集合,reducer就是將集合中的值merge成一個值(好比求和等,這個值也能夠是一個新的集合,那麼所謂的值就是集合)。QtConcurrent::run()等方法都會返回一個QFuture<T>類型的對象,直接訪問該對象將會阻塞,那麼就要使用非阻塞的QFutureWatcher<T>來監視,它所監視的事件有paused, resumed, canceled。其中QFuture<T>中的T須要是filter等操做的結果類型,QFutureWatcher<T>就須要和他監視的QFuture的類型相同纔對。來看一下《Qt高級編程》中的示意圖:
這裏有一個地方要注意:在filtered()以前,咱們就應該將watcher的信號連接到相應的槽上去。下面是具體用法(以filtered爲例):
函數原型
說明前邊的參數能夠是一個集合,或者是兩個const的迭代器,最後一個參數就是相應的過濾函數。過濾函數的圓形須要是這樣的:
bool function(const T &t);
用法以下(來自Qt Assistant):
bool allLowerCase(const QString &string) { return string.lowered() == string; } QStringList strings = ...; QFuture<QString> lowerCaseStrings = QtConcurrent::filtered(strings, allLowerCase);
這裏學會了一種叫函數對象的東東,上一段代碼先看,其實就是重載()操做符,另外typedef 該操做符函數的返回值爲result_type。
struct StartsWith { StartsWith(const QString &string) : m_string(string) { } typedef bool result_type; bool operator()(const QString &testString) { return testString.startsWith(m_string); } QString m_string; }; QList<QString> strings = ...; QFuture<QString> fooString = QtConcurrent::filtered(images, StartsWith(QLatin1String("Foo")));
好,這部分就作這麼多記錄,而後準備把QtCreator的ProgressManager摳出來用。