1. 程式人生 > 實用技巧 >Qt 多執行緒之QtConcurrent::map(處理序列容器)

Qt 多執行緒之QtConcurrent::map(處理序列容器)

QtConcurrent::map()、QtConcurrent::mapped() 和 QtConcurrent::mappedReduced() 函式對一個序列中(例如:QList、QVector)的專案並行地進行計算。

1、map函式

map函式的功能是在其他執行緒執行指定的函式,map函式有兩個引數

第一個是集合

第二個引數是一個函式。它的作用就是同時用第二個引數來計算第一個引數中的每一個元素,且結果直接覆蓋到元素中,如果是成員函式,那要靜態成員函式才能執行

 1 //靜態函式
 2 void Widget::Func(QPushButton * & btn)
 3 {
 4     QTime time = QTime::currentTime();
5 qsrand(time.msec() + time.second()*1000); 6 btn->setText(QString("按鈕_%1").arg(qrand() % 20)); 7 qDebug()<<"thread ID"<<QThread::currentThreadId(); 8 } 9 10 void Widget::on_pushButton_clicked() 11 { 12 QList<QPushButton*> list = this->findChildren<QPushButton*>();
13 QFuture<void> f = QtConcurrent::map(list,&Widget::Func); //map函式 不能執行非靜態成員函式 14 f.waitForFinished(); 15 }

結果:

2、mapped函式

mapped函式的作用和map類似,只是把計算結果放到了新的容器中

例子1:

 1 int func2(int a)
 2 {
 3     return a + 1;
 4 }
 5  
 6 void Widget::on_pushButton_clicked()
 7 {
 8     QList<int
> alist; 9 alist<<1<<3<<5<<7<<9; 10 11 QFuture<int> f = QtConcurrent::mapped(alist,func2); //QFuture的型別為int 12 f.waitForFinished(); 13 qDebug()<<"alist"<<alist; 14 QList<int> newlist = f.results(); 15 qDebug()<<"newlist"<<newlist; 16 }

結果:

例子2:

 1 QPushButton* Widget::Func2(QPushButton * btn)
 2 {
 3     QThread::msleep(200);
 4     QTime time = QTime::currentTime();
 5     qsrand(time.msec() + time.second()*1000);
 6     btn->setText(QString("按鈕_%1").arg(qrand() % 20));
 7     qDebug()<<"thread ID"<<QThread::currentThreadId();
 8     return btn;
 9 }
10  
11 void Widget::on_pushButton_clicked()
12 {
13     QList<QPushButton*> list = this->findChildren<QPushButton*>();
14     QFuture<QPushButton*> f2 = QtConcurrent::mapped(list,&Widget::Func2);
15     f2.waitForFinished();
16 }

結果:

關於mapped,官方有個:Image Scaling Example例子,通過一次性載入多張圖片,分別轉成100*100的縮圖顯示在介面來演示mapped的使用。

 1 //圖片轉換成100*100的圖片
 2 QImage scale(const QString &imageFileName)
 3 {
 4     QImage image(imageFileName);
 5     QThread::msleep(500);
 6     return image.scaled(QSize(imageSize, imageSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
 7 }
 8  
 9 QFutureWatcher imageScaling = new QFutureWatcher<QImage>(this);
10 connect(imageScaling, &QFutureWatcherBase::resultReadyAt, this, &Images::showImage);
11 connect(imageScaling, &QFutureWatcherBase::finished, this, &Images::finished);
12  
13 QStringList files;//圖片地址
14 imageScaling->setFuture(QtConcurrent::mapped(files, scale));
15  
16 connect(pauseButton, &QAbstractButton::clicked, imageScaling, &QFutureWatcherBase::togglePaused);//暫停/恢復操作

QFutureWatcherBase::togglePaused 暫停/繼續操作

QFutureWatcherBase::resultReadyAt 讀取到一個結果

可以用這些訊號來設定處理的進度條

用這些訊號設定進度條官方正好有個叫:QtConcurrent Progress Dialog Example的簡單例子:

 1 void spin(int &iteration)
 2 {
 3     volatile int v = 0;
 4     for (int j = 0; j < 400000000; ++j)
 5         ++v;
 6     qDebug() << "處理值" << iteration << "的執行緒:" << QThread::currentThreadId();
 7 }
 8  
 9 int main(int argc, char **argv)
10 {
11     QApplication app(argc, argv);
12  
13     QVector<int> vector;
14     for (int i = 0; i < 20; ++i)
15         vector.append(i);
16  
17     QProgressDialog dialog;
18     dialog.setLabelText(QString("正在使用 %1 個執行緒...").arg(QThread::idealThreadCount()));
19  
20     QFutureWatcher<void> futureWatcher;
21     QObject::connect(&futureWatcher, &QFutureWatcherBase::finished, &dialog, &QProgressDialog::reset);
22     QObject::connect(&dialog, &QProgressDialog::canceled, &futureWatcher, &QFutureWatcherBase::cancel);
23     QObject::connect(&futureWatcher, &QFutureWatcherBase::progressRangeChanged, &dialog, &QProgressDialog::setRange);
24     QObject::connect(&futureWatcher, &QFutureWatcherBase::progressValueChanged, &dialog, &QProgressDialog::setValue);
25  
26     futureWatcher.setFuture(QtConcurrent::map(vector, spin));
27     dialog.exec();
28     futureWatcher.waitForFinished();
29     qDebug() << "Canceled?" << futureWatcher.future().isCanceled();
30 }

3、mappedReduced函式

mappedReduced函式比mapped多一個引數,這個引數也是個函式。作用就是將mapped出來的結果再計算最終得出一個值。

 1 int func3(int a)
 2 {
 3     return a + 1;
 4 }
 5  
 6 void sum(int& result, const int& b)
 7 {
 8     result += b;
 9 }
10  
11 void Widget::on_pushButton_clicked()
12 {
13     QList<int> alist;
14     alist<<1<<3<<5<<7<<9;
15  
16     QFuture<int> result = QtConcurrent::mappedReduced(alist,func3,sum);
17     result.waitForFinished();
18     qDebug()<<result.result();
19 }

alist中的一個值執行完func3馬上執行sum,而不是alist中所有之都執行完才執行sum。

結果:

關於mappedReduced,官方的demo中有個叫做QtConcurrent Word Count Example的例子通過單執行緒/多執行緒統計資料夾中單詞個數來演示mappedReduced的使用。

 1 //遍歷資料夾,返回資料夾內所有檔名
 2 QStringList findFiles(const QString &startDir, QStringList filters)
 3 {
 4     QStringList names;
 5     QDir dir(startDir);
 6  
 7     foreach (QString file, dir.entryList(filters, QDir::Files))
 8         names += startDir + "/" + file;
 9  
10     foreach (QString subdir, dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot))
11         names += findFiles(startDir + "/" + subdir, filters);
12     return names;
13 }
14  
15 //單執行緒計算此檔案列表中每個檔案的單詞個數:<檔名,單詞個數>
16 QMap<QString, int> singleThreadedWordCount(QStringList & files)
17 {
18     QMap<QString, int> wordCount;
19     QString word;
20     foreach (QString file, files)
21     {
22         QFile f(file);
23         f.open(QIODevice::ReadOnly);
24         QTextStream textStream(&f);
25         while (textStream.atEnd() == false)
26             foreach(word, textStream.readLine().split(" "))
27                 wordCount[word] += 1;
28     }
29     return wordCount;
30 }
31  
32 //計算一個檔案中單詞數
33 QMap<QString, int> countWords(const QString &file)
34 {
35     QFile f(file);
36     f.open(QIODevice::ReadOnly);
37     QTextStream textStream(&f);
38     QMap<QString, int> wordCount;
39  
40     while (textStream.atEnd() == false)
41         foreach (QString word, textStream.readLine().split(" "))
42             wordCount[word] += 1;
43  
44     return wordCount;
45 }
46  
47 void reduce(QMap<QString, int> &result, const QMap<QString, int> &w)
48 {
49     QMapIterator<QString, int> it(w);
50     while (it.hasNext())
51     {
52         it.next();
53         result[it.key()] += it.value();
54     }
55 }
 1 int main(int argc, char** argv)
 2 {
 3     QApplication app(argc, argv);
 4     qDebug() << "正在查詢檔案...";
 5     QStringList files = findFiles("../../",
 6                                   QStringList() << "*.cpp" << "*.h");//查詢此格式的檔案
 7     QTime time;
 8     time.start();
 9     QMap<QString, int> total = singleThreadedWordCount(files);
10  
11     int singleThreadTime = 0;
12     {
13         QTime time;
14         time.start();
15         QMap<QString, int> total = singleThreadedWordCount(files);
16         singleThreadTime = time.elapsed();
17         qDebug() << "單執行緒統計這些檔案單詞數所需時間:" << singleThreadTime;
18     }
19  
20     int mapReduceTime = 0;
21     {
22         QTime time;
23         time.start();
24         QMap<QString, int> total = mappedReduced(files, countWords, reduce);
25         mapReduceTime = time.elapsed();
26         qDebug() << "MapReduce" << mapReduceTime;
27     }
28     qDebug() << "速度提升倍數:" << ((double)singleThreadTime - (double)mapReduceTime) / (double)mapReduceTime + 1;
29 }
QMap<QString, int> total = mappedReduced(files, countWords, reduce);

這句在多執行緒裡對檔案列表中的每個檔案執行統計單詞操作(執行countWords),然後把統計結果存到QMap中(執行reduce)

列印reduce()中的result的地址可以發現result一直是同一個,猜測:這個QMap<QString, int> &result是mappedReduced申請的,執行reduce時候把資料都儲存到這個result裡,執行完mappedReduced了就返回值