閱讀QtCreator--Concurrent預備知識

在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

image

提升了將近三倍的速度。多線程

這裏分爲三個部分來談Concurrent在Qt中的用法(QThread在這裏就不談了,有點low-level,靈活性雖強,但很差控制,須要較好的多線程經驗)。併發

1. QtConcurrent::run()

QtConcurrent::run()使用Qt的全局線程池,找到一個空閒線程執行函數。在操做系統中,是有流水線技術的,那麼IO操做與CPU運算是能夠同時進行的,若是咱們的操做中有這兩種操做,就可使用QtConcurrent併發完成。看下面的函數原型:app

QFuture<T> QtConcurrent::run ( Function function, ... )

第一個是函數名,接下來是不定長的參數列表。每一個線程就去執行這個函數,直到完成任務線程歸還給線程池。這裏就有一個問題,假如我如今有不少任務,那麼,須要多少個線程來作這些任務呢?固然,並非開闢的線程越多越好的,開闢過多的線程不只浪費資源,同時也會使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

bool QMetaObject::invokeMethod ( QObject * obj, const char * member, Qt::ConnectionType type,QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( 0 ), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument() ) [static]。
也就是說invokeMethod最多支持10個參數,該函數還有很多的重載。具體使用方法以下:

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纔會有效。性能

 

2. 使用QRunnable

使用QRunnable就須要派生一個子類了,而後重寫虛函數run()。在run函數中完成要作的任務,可是QRunnable不是QObject的子類,那麼只能用invoke方法了和自定義事件(custom event)。另外在run結束以後,線程池將會刪除這個runnable對象,那麼在構造函數裏使用setAutoDelete(false)能夠將控制權轉交給用戶。建立任務時,使用的方法是QThreadPool::globalInstance()->start(MyRunnableObj);測試

使用QtConcurrent::run()方法能夠返回一個QFuture<T>, 咱們可使用QFutureWatcher<T>來跟蹤處理的過程,而使用QRunnable類就須要咱們本身管理了。

3. 使用QtConcurrent監視進度

有四種方式來監視任務進度,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高級編程》中的示意圖:

image

這裏有一個地方要注意:在filtered()以前,咱們就應該將watcher的信號連接到相應的槽上去。下面是具體用法(以filtered爲例):

函數原型

QFuture<T> QtConcurrent::filtered ( const Sequence & sequence, FilterFunction filterFunction );
QFuture<T> QtConcurrent::filtered ( ConstIterator begin, ConstIterator end, FilterFunction filterFunction )

說明前邊的參數能夠是一個集合,或者是兩個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摳出來用。

相關文章
相關標籤/搜索