Qt Concurrent Framework

2017/7/31 10:47:42


Qt Concurrent Framework

Introduction

The QtConcurrent namespace provides high-level APIs that make it possible to write multi-threaded programs without using low-level threading primitives such as mutexes, read-write locks, wait conditions, or semaphores. Programs written with QtConcurrent automatically adjust the number of threads used according to the number of processor cores available. This means that applications written today will continue to scale when deployed on multi-core systems in the future. QtConcurrent functions such as Map-Reduce and Filter-Reduce are deployed on non-distributed systems.

Concurrent Map and Map-Reduce

The QtConcurrent::map(), QtConcurrent::mapped() and QtConcurrent::mappedReduced() functions run computations in parallel on the items in a sequence such as a QList or a QVector.

Concurrent Map

QtConcurrent::mapped():

Concurrent map takes an input sequence and a map function. This map functions is then called for each item in the sequence, and a new sequence containing the return values from the map function is returned. The map functions must be the form:

U function( const T &t );

U is the return value and T should be consistent with the type stored in the sequence.

QImage scaled(const QImage &image)
{
    return image.scaled(100, 100);
}

QList<QImage> images = ...;
QFuture<QImage> thumbnails = QtConcurrent::mapped(images, scaled); 

QtConcurrent::map() modify a sequence in-place. The map function is:

U function( T &t );

The return value and return type are not used.

void scale(QImage &image)
{
    image = image.scaled(100, 100);
}

QList<QImage> images = ...;
QFuture<void> future = QtConcurrent::map(images, scale);

Since the sequence is modified in place, QtConcurrent::map() does not return any results via QFuture. However, you can still use QFuture and QFutureWatcher to monitor the status of the map.

Concurrent Map-Reduce

QtConcurrent::mappedReduced() is similar to QtConcurrent::mapped(), but instead of returning a sequence with the new results, the results are combined into a single value using a reduce function. The reduce function must be of the form:

V function(T &result, const U &intermediate)

T is the type of the final result, U is the return type of the map function. The return value and return type of the reduce function are not used. Examples:

#include <QCoreApplication>
#include <QtConcurrent/QtConcurrentMap>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QMap>
#include <QStringList>
#include <QTextStream>
#include <QThread>
#include <QTime>


typedef QMap<QString, int> WordCounter;

QStringList findFiles( const QString &startDir, const QStringList &filters ) {
    QStringList names;
    QDir dir( startDir );
    foreach ( const QString &file, dir.entryList( filters, QDir::Files ) ) {
        names += startDir + '/' + file;
    }

    foreach ( const QString &subdir, dir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot ) ) {
        names += findFiles( startDir + '/' + subdir, filters );
    }
    return names;
}

WordCounter singleThreadFind( const QStringList &files ) {
    WordCounter wordCounter;
    foreach ( const QString &file, files ) {
        QFile f( file );
        f.open(QIODevice::ReadOnly);
        QTextStream textStream( &f );
        while ( !textStream.atEnd() ) {
            foreach ( const QString &word , textStream.readLine().split(' ') ) {
                wordCounter[word] += 1;
            }
        }
    }
    return wordCounter;
}

WordCounter countWords( const QString &file ) {
    WordCounter wordCounter;
    QFile f( file );
    f.open( QIODevice::ReadOnly );
    QTextStream textstream( &f );
    while ( !textstream.atEnd() ) {
        foreach ( const QString &word, textstream.readLine().split( ' ' ) ) {
            wordCounter[word] += 1;
        }
    }
    return wordCounter;
}

void reduce( WordCounter &result, const WordCounter &w ) {
    // Jave-style iterator
    QMapIterator<QString, int> iter( w );
    while ( iter.hasNext() ) {
        iter.next();
        result[iter.key()] = iter.value();
    }
}

int main( int argc, char **argv ) {
    QCoreApplication app( argc, argv );

    QTime time;

    // find files
    time.start();
    const QStringList &files = findFiles( "../../../", QStringList() << "*.cpp" << "*.h" );
    qDebug() << "Find " << files.size() << "files, using " << time.elapsed() / 1000.0 << "seconds";

    // single thread test, 320s+
    {
        qDebug() << "Single thread";
        time.start();
        WordCounter words = singleThreadFind( files );
        qDebug() << "Find " << words.size() << "Using " << time.elapsed() / 1000.0 << "seconds";
    }

    // concurrent map reduced, 80s
    {
        qDebug() << "Multi-thread";
        time.start();
        WordCounter words = QtConcurrent::mappedReduced( files, countWords, reduce );
        qDebug() << "Find " << words.size() << "Using " << time.elapsed() / 1000.0 << "seconds";
    }

    return 0;
}

Additional APIs

  • Using Iterators instead of Sequence
  • Blocking Variants
  • Using Member Functions
  • Using Function Objects
  • Wrapping Functions that Take Multiple Arguments

Concurrent Run

The QtConcurrent::run() function runs a function in a separate thread. The return value of the function is made available through the QFuture API.

Basic Usage

To run a function in another thread, use QtConcurrent::run():

extern void aFunction();
QFuture<void> future = QtConcurrent::run(aFunction);

This will run aFunction in a separate thread obtained from the default QThreadPool. You can use the QFuture and QFutureWatcher classes to monitor the status of the function.

Run in thread pool

To use a dedicated thread pool, you can pass the QThreadPool as the first argument:

extern void aFunction();
QThreadPool pool;
QFuture<void> future = QtConcurrent::run(&pool, aFunction);

Pass arguments

Passing arguments to the function is done by adding them to the QtConcurrent::run() call immediately after the function name. For example:

extern void aFunctionWithArguments(int arg1, double arg2, const QString &string);

int integer = ...;
double floatingPoint = ...;
QString string = ...;

QFuture<void> future = QtConcurrent::run(aFunctionWithArguments, integer, floatingPoint, string);

A copy of each argument is made at the point where QtConcurrent::run() is called, and these values are passed to the thread when it begins executing the function. Changes made to the arguments after calling QtConcurrent::run() are not visible to the thread.

Return values

Any return value from the function is available via QFuture:

extern QString functionReturningAString();
QFuture<QString> future = QtConcurrent::run(functionReturningAString);
...
QString result = future.result();

Run example

#include <QCoreApplication>
#include <QtConcurrent/QtConcurrentRun>
#include <QDebug>
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
#include <iostream>
#include <functional>

int counter = 0;
QMutex mutex;

void foo_1() {
    // qDebug() is not thread-safe
    // https://stackoverflow.com/questions/22527253/is-qdebug-thread-safe
    std::cout << QThread::currentThreadId() << "\n";
    QMutexLocker locker( &mutex );
    for ( int i = 0; i < 1000000; ++i ) {
        counter ++;
    }
}

void foo_2() {
    std::cout << QThread::currentThreadId() << "\n";
    QMutexLocker locker( &mutex );
    for ( int i = 0; i < 2000000; ++i ) {
        counter ++;
    }
}



int main( int argc, char **argv ) {
    QCoreApplication app( argc, argv );
    QFuture<void> result_1 = QtConcurrent::run( foo_1 );
    QFuture<void> result_2 = QtConcurrent::run( foo_2 );
    QString str = "Hello world";
    QFuture<QStringList> result_3 =
            QtConcurrent::run( [=]() {
        return str.split(" ");
    } );

    result_1.waitForFinished();
    result_2.waitForFinished();
    QStringList value = result_3.result();

    std::cout << counter << "\n";
    foreach ( const QString &s, value ) {
        std::cout << s.toStdString() << "\n";
    }

    return 0;
}

Helper Class

QFuture

The QFuture class represents the result of an asynchronous computation. QFuture allows threads to be synchronized against one or more results which will be ready at a later point in time. If the result is not immediately available, this function will block and wait for the result to become available. Key functions:

  • T QFuture::result() const: Returns the first result in the future.
  • T QFuture::resultAt(int index) const: Returns the result at index in the future.

QFuture also offers ways to interact with a running computation. Key functions:

  • void QFuture::resume(): Resumes the asynchronous computation represented by this future.
  • void QFuture::setPaused(bool paused)
  • void QFuture::togglePaused()
  • void QFuture::cancel()

QFuture<void> is specialized to not contain any of the result fetching functions. Any QFuture<T> can be assigned or copied into a QFuture<void> as well. This is useful if only status or progress information is needed - not the actual result data.

QFutureWatcher

The QFutureWatcher class allows monitoring a QFuture using signals and slots. QFutureWatcher provides information and notifications about a QFuture. Use the setFuture() function to start watching a particular QFuture.
QFutureWatcher provides lots of signals such as:

  • void QFutureWatcher::canceled()
  • void QFutureWatcher::finished()
  • void QFutureWatcher::paused()
  • void QFutureWatcher::progressValueChanged(int progressValue)
  • void QFutureWatcher::resultReadyAt(int index)
  • void QFutureWatcher::resultsReadyAt(int beginIndex, int endIndex)

Also, QFutureWatcher provides some convenient slots:

  • void cancel()
  • void pause()
  • void resume()
  • void setPaused(bool paused)
  • void togglePaused()

QFutureWatcher starts watching the given future by void QFutureWatcher::setFuture(const QFuture<T> &future). To avoid a race condition, it is important to call this function after doing the connections. Also, some functions are provided to report status.

Examples

Here will list a thumbnails generation example using QConcurrent framework.

#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H

#include <QWidget>
#include <QtConcurrent>
#include <QLabel>
#include <QList>
#include <QImage>

namespace Ui {
class ImageViewer;
}

class ImageViewer : public QWidget
{
    Q_OBJECT
public:
    explicit ImageViewer(QWidget *parent = 0);

signals:

private slots:
    void open();
    void finished();
    void showImage( int i );

private:
    QFutureWatcher<QImage> *m_watcher;
    QList<QLabel *> m_labels;
    Ui::ImageViewer *m_ui;
};

#endif // IMAGEVIEWER_H

#include "ImageViewer.h"
#include <QFileDialog>
#include "ui_ImageViewer.h"

static const int IMG_SIZE = 50;
QImage scale( const QString &path ) {
    QImage image( path );
    return image.scaled( QSize( IMG_SIZE, IMG_SIZE ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
}

ImageViewer::ImageViewer(QWidget *parent) :
    QWidget(parent), m_ui( new Ui::ImageViewer ) {
    m_ui->setupUi( this );
    m_watcher = new QFutureWatcher<QImage>();

    connect( m_ui->m_openBtn, SIGNAL(clicked()), SLOT(open()) );
    connect( m_ui->m_pauseBtn, SIGNAL(clicked()), m_watcher, SLOT(togglePaused()) );
    connect( m_ui->m_cancelBtn, SIGNAL(clicked()), m_watcher, SLOT(cancel()) );

    // do connection before setFuture for future watcher
    connect( m_watcher, SIGNAL(resultReadyAt(int)), this, SLOT(showImage(int)) );
    connect( m_watcher, SIGNAL(finished()), this, SLOT(finished()) );
}

void ImageViewer::open() {
    if ( m_watcher->isRunning() ) {
        m_watcher->cancel();
        m_watcher->waitForFinished();
    }

    const QStringList &files = QFileDialog::getOpenFileNames( this, "Select Images",
                                   QStandardPaths::writableLocation( QStandardPaths::PicturesLocation ),
                                   "*.jpg *.png" );
    if ( files.isEmpty() ) {
        return;
    }

    qDeleteAll( m_labels );
    m_labels.clear();
    int dim = qSqrt( files.size() ) + 1;
    for ( int i = 0; i < dim; ++i ) {
        for ( int j = 0; j < dim; ++j ) {
            QLabel *label = new QLabel;
            label->setFixedWidth( IMG_SIZE );
            m_labels.append( label );
            m_ui->m_gridLayout->addWidget( label, i, j );
        }
    }

    m_watcher->setFuture( QtConcurrent::mapped( files, scale ) );

    m_ui->m_openBtn->setEnabled( false );
    m_ui->m_pauseBtn->setEnabled( true );
    m_ui->m_cancelBtn->setEnabled( true );
}

void ImageViewer::finished() {
    m_ui->m_openBtn->setEnabled( true );
    m_ui->m_pauseBtn->setEnabled( false );
    m_ui->m_cancelBtn->setEnabled( false );
}

void ImageViewer::showImage( int i ) {
    QLabel *image = m_labels[i];
    if ( !image ) {
        return;
    }
    image->setPixmap( QPixmap::fromImage( m_watcher->resultAt(i) ) );
}

References

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兔毙,一起剝皮案震驚了整個(gè)濱河市唾琼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌澎剥,老刑警劉巖锡溯,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異哑姚,居然都是意外死亡祭饭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進(jìn)店門叙量,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倡蝙,“玉大人,你說我怎么就攤上這事绞佩∷屡福” “怎么了猪钮?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胆建。 經(jīng)常有香客問我烤低,道長,這世上最難降的妖魔是什么笆载? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任扑馁,我火速辦了婚禮,結(jié)果婚禮上宰译,老公的妹妹穿的比我還像新娘檐蚜。我一直安慰自己,他們只是感情好沿侈,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布闯第。 她就那樣靜靜地躺著,像睡著了一般缀拭。 火紅的嫁衣襯著肌膚如雪咳短。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天蛛淋,我揣著相機(jī)與錄音咙好,去河邊找鬼。 笑死褐荷,一個(gè)胖子當(dāng)著我的面吹牛勾效,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叛甫,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼层宫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了其监?” 一聲冷哼從身側(cè)響起萌腿,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抖苦,沒想到半個(gè)月后毁菱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锌历,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年贮庞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片究西。...
    茶點(diǎn)故事閱讀 40,769評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窗慎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怔揩,到底是詐尸還是另有隱情捉邢,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布商膊,位于F島的核電站伏伐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏晕拆。R本人自食惡果不足惜藐翎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望实幕。 院中可真熱鬧吝镣,春花似錦、人聲如沸昆庇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽整吆。三九已至拱撵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間表蝙,已是汗流浹背拴测。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留府蛇,地道東北人集索。 一個(gè)月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像汇跨,于是被迫代替她去往敵國和親务荆。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容

  • **2014真題Directions:Read the following text. Choose the be...
    又是夜半驚坐起閱讀 9,583評論 0 23
  • 每一次相遇 都是久別重逢 說好的來世相見 若一句恒古的誓言 那一日 驚詫于終于遇見 那一日 痛苦于你情我愿 最熟悉...
    冉燈閱讀 840評論 2 9
  • 恩和早晚溫差挺大的扰法,或許是臨著大興安嶺的緣故 早晨起來 一掀被子 就感到濕冷的空氣撲面而來 化完妝疊好被子 打開房...
    一顆玉米豆閱讀 305評論 0 0
  • 01.今天的課例是《珍珠鳥》蛹含。第一課時(shí)由于老師的得意門生戴建榮老師執(zhí)教,秉承了于老師多朗讀的課堂亮點(diǎn)塞颁,注重細(xì)節(jié)引導(dǎo)...
    王艷粉閱讀 326評論 0 0