forked from qt-creator/qt-creator
File search: Avoid use of QtConcurrent
QtConcurrent limits resource usage to a global number of simultaneous threads. That means that if some QtConcurrent based algorithm currently grabs all threads, any other use of QtConcurrent blocks, which is not what we want. Use the new threading methods of C++11 instead, but still use QFuture(Interface) manually for the progress, result and status reporting. Task-number: QTCREATORBUG-14640 Change-Id: I6379d2f2a01b6d200811ef4be0bbfcd4493dd154 Reviewed-by: Orgad Shaneh <orgads@gmail.com>
This commit is contained in:
@@ -35,7 +35,6 @@
|
|||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QTextCodec>
|
#include <QTextCodec>
|
||||||
#include <QtConcurrentMap>
|
|
||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|
||||||
@@ -88,13 +87,12 @@ class FileSearch
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FileSearch(const QString &searchTerm, QTextDocument::FindFlags flags,
|
FileSearch(const QString &searchTerm, QTextDocument::FindFlags flags,
|
||||||
QMap<QString, QString> fileToContentsMap,
|
QMap<QString, QString> fileToContentsMap);
|
||||||
QFutureInterface<FileSearchResultList> *futureInterface);
|
FileSearchResultList operator()(QFutureInterface<FileSearchResultList> futureInterface,
|
||||||
const FileSearchResultList operator()(const FileIterator::Item &item) const;
|
const FileIterator::Item &item) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMap<QString, QString> fileToContentsMap;
|
QMap<QString, QString> fileToContentsMap;
|
||||||
QFutureInterface<FileSearchResultList> *future;
|
|
||||||
QString searchTermLower;
|
QString searchTermLower;
|
||||||
QString searchTermUpper;
|
QString searchTermUpper;
|
||||||
int termMaxIndex;
|
int termMaxIndex;
|
||||||
@@ -109,27 +107,25 @@ class FileSearchRegExp
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FileSearchRegExp(const QString &searchTerm, QTextDocument::FindFlags flags,
|
FileSearchRegExp(const QString &searchTerm, QTextDocument::FindFlags flags,
|
||||||
QMap<QString, QString> fileToContentsMap,
|
QMap<QString, QString> fileToContentsMap);
|
||||||
QFutureInterface<FileSearchResultList> *futureInterface);
|
FileSearchRegExp(const FileSearchRegExp &other);
|
||||||
const FileSearchResultList operator()(const FileIterator::Item &item) const;
|
FileSearchResultList operator()(QFutureInterface<FileSearchResultList> futureInterface,
|
||||||
|
const FileIterator::Item &item) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QRegularExpressionMatch doGuardedMatch(const QString &line, int offset) const;
|
QRegularExpressionMatch doGuardedMatch(const QString &line, int offset) const;
|
||||||
|
|
||||||
QMap<QString, QString> fileToContentsMap;
|
QMap<QString, QString> fileToContentsMap;
|
||||||
QFutureInterface<FileSearchResultList> *future;
|
|
||||||
QRegularExpression expression;
|
QRegularExpression expression;
|
||||||
mutable QMutex mutex;
|
mutable QMutex mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
FileSearch::FileSearch(const QString &searchTerm, QTextDocument::FindFlags flags,
|
FileSearch::FileSearch(const QString &searchTerm, QTextDocument::FindFlags flags,
|
||||||
QMap<QString, QString> fileToContentsMap,
|
QMap<QString, QString> fileToContentsMap)
|
||||||
QFutureInterface<FileSearchResultList> *futureInterface)
|
|
||||||
{
|
{
|
||||||
this->fileToContentsMap = fileToContentsMap;
|
this->fileToContentsMap = fileToContentsMap;
|
||||||
caseSensitive = (flags & QTextDocument::FindCaseSensitively);
|
caseSensitive = (flags & QTextDocument::FindCaseSensitively);
|
||||||
wholeWord = (flags & QTextDocument::FindWholeWords);
|
wholeWord = (flags & QTextDocument::FindWholeWords);
|
||||||
future = futureInterface;
|
|
||||||
searchTermLower = searchTerm.toLower();
|
searchTermLower = searchTerm.toLower();
|
||||||
searchTermUpper = searchTerm.toUpper();
|
searchTermUpper = searchTerm.toUpper();
|
||||||
termMaxIndex = searchTerm.length() - 1;
|
termMaxIndex = searchTerm.length() - 1;
|
||||||
@@ -138,10 +134,11 @@ FileSearch::FileSearch(const QString &searchTerm, QTextDocument::FindFlags flags
|
|||||||
termDataUpper = searchTermUpper.constData();
|
termDataUpper = searchTermUpper.constData();
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileSearchResultList FileSearch::operator()(const FileIterator::Item &item) const
|
FileSearchResultList FileSearch::operator()(QFutureInterface<FileSearchResultList> futureInterface,
|
||||||
|
const FileIterator::Item &item) const
|
||||||
{
|
{
|
||||||
FileSearchResultList results;
|
FileSearchResultList results;
|
||||||
if (future->isCanceled())
|
if (futureInterface.isCanceled())
|
||||||
return results;
|
return results;
|
||||||
QFile file;
|
QFile file;
|
||||||
QTextStream stream;
|
QTextStream stream;
|
||||||
@@ -212,9 +209,9 @@ const FileSearchResultList FileSearch::operator()(const FileIterator::Item &item
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (future->isPaused())
|
if (futureInterface.isPaused())
|
||||||
future->waitForResume();
|
futureInterface.waitForResume();
|
||||||
if (future->isCanceled())
|
if (futureInterface.isCanceled())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (file.isOpen())
|
if (file.isOpen())
|
||||||
@@ -223,11 +220,9 @@ const FileSearchResultList FileSearch::operator()(const FileIterator::Item &item
|
|||||||
}
|
}
|
||||||
|
|
||||||
FileSearchRegExp::FileSearchRegExp(const QString &searchTerm, QTextDocument::FindFlags flags,
|
FileSearchRegExp::FileSearchRegExp(const QString &searchTerm, QTextDocument::FindFlags flags,
|
||||||
QMap<QString, QString> fileToContentsMap,
|
QMap<QString, QString> fileToContentsMap)
|
||||||
QFutureInterface<FileSearchResultList> *futureInterface)
|
|
||||||
{
|
{
|
||||||
this->fileToContentsMap = fileToContentsMap;
|
this->fileToContentsMap = fileToContentsMap;
|
||||||
future = futureInterface;
|
|
||||||
QString term = searchTerm;
|
QString term = searchTerm;
|
||||||
if (flags & QTextDocument::FindWholeWords)
|
if (flags & QTextDocument::FindWholeWords)
|
||||||
term = QString::fromLatin1("\\b%1\\b").arg(term);
|
term = QString::fromLatin1("\\b%1\\b").arg(term);
|
||||||
@@ -236,16 +231,23 @@ FileSearchRegExp::FileSearchRegExp(const QString &searchTerm, QTextDocument::Fin
|
|||||||
expression = QRegularExpression(term, patternOptions);
|
expression = QRegularExpression(term, patternOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileSearchRegExp::FileSearchRegExp(const FileSearchRegExp &other)
|
||||||
|
: fileToContentsMap(other.fileToContentsMap),
|
||||||
|
expression(other.expression)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
QRegularExpressionMatch FileSearchRegExp::doGuardedMatch(const QString &line, int offset) const
|
QRegularExpressionMatch FileSearchRegExp::doGuardedMatch(const QString &line, int offset) const
|
||||||
{
|
{
|
||||||
QMutexLocker lock(&mutex);
|
QMutexLocker lock(&mutex);
|
||||||
return expression.match(line, offset);
|
return expression.match(line, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileSearchResultList FileSearchRegExp::operator()(const FileIterator::Item &item) const
|
FileSearchResultList FileSearchRegExp::operator()(QFutureInterface<FileSearchResultList> futureInterface,
|
||||||
|
const FileIterator::Item &item) const
|
||||||
{
|
{
|
||||||
FileSearchResultList results;
|
FileSearchResultList results;
|
||||||
if (future->isCanceled())
|
if (futureInterface.isCanceled())
|
||||||
return results;
|
return results;
|
||||||
QFile file;
|
QFile file;
|
||||||
QTextStream stream;
|
QTextStream stream;
|
||||||
@@ -273,9 +275,9 @@ const FileSearchResultList FileSearchRegExp::operator()(const FileIterator::Item
|
|||||||
if (pos >= lengthOfLine)
|
if (pos >= lengthOfLine)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (future->isPaused())
|
if (futureInterface.isPaused())
|
||||||
future->waitForResume();
|
futureInterface.waitForResume();
|
||||||
if (future->isCanceled())
|
if (futureInterface.isCanceled())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (file.isOpen())
|
if (file.isOpen())
|
||||||
@@ -283,140 +285,90 @@ const FileSearchResultList FileSearchRegExp::operator()(const FileIterator::Item
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RunFileSearch
|
struct SearchState
|
||||||
{
|
{
|
||||||
public:
|
SearchState(const QString &term, FileIterator *iterator) : searchTerm(term), files(iterator) {}
|
||||||
RunFileSearch(QFutureInterface<FileSearchResultList> &future,
|
QString searchTerm;
|
||||||
const QString &searchTerm,
|
FileIterator *files = 0;
|
||||||
FileIterator *files,
|
FileSearchResultList cachedResults;
|
||||||
const std::function<FileSearchResultList(FileIterator::Item)> &searchFunction);
|
int numFilesSearched = 0;
|
||||||
|
int numMatches = 0;
|
||||||
void run();
|
|
||||||
void collect(const FileSearchResultList &results);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QFutureInterface<FileSearchResultList> &m_future;
|
|
||||||
QString m_searchTerm;
|
|
||||||
FileIterator *m_files;
|
|
||||||
std::function<FileSearchResultList(FileIterator::Item)> m_searchFunction;
|
|
||||||
|
|
||||||
int m_numFilesSearched;
|
|
||||||
int m_numMatches;
|
|
||||||
FileSearchResultList m_results;
|
|
||||||
bool m_canceled;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
RunFileSearch::RunFileSearch(QFutureInterface<FileSearchResultList> &future,
|
SearchState initFileSearch(QFutureInterface<FileSearchResultList> &futureInterface,
|
||||||
const QString &searchTerm, FileIterator *files,
|
const QString &searchTerm, FileIterator *files)
|
||||||
const std::function<FileSearchResultList (FileIterator::Item)> &searchFunction)
|
|
||||||
: m_future(future),
|
|
||||||
m_searchTerm(searchTerm),
|
|
||||||
m_files(files),
|
|
||||||
m_searchFunction(searchFunction),
|
|
||||||
m_numFilesSearched(0),
|
|
||||||
m_numMatches(0),
|
|
||||||
m_canceled(false)
|
|
||||||
{
|
{
|
||||||
m_future.setProgressRange(0, m_files->maxProgress());
|
futureInterface.setProgressRange(0, files->maxProgress());
|
||||||
m_future.setProgressValueAndText(m_files->currentProgress(), msgFound(m_searchTerm,
|
futureInterface.setProgressValueAndText(files->currentProgress(), msgFound(searchTerm, 0, 0));
|
||||||
m_numMatches,
|
return SearchState(searchTerm, files);
|
||||||
m_numFilesSearched));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RunFileSearch::run()
|
void collectSearchResults(QFutureInterface<FileSearchResultList> &futureInterface,
|
||||||
|
SearchState &state,
|
||||||
|
const FileSearchResultList &results)
|
||||||
{
|
{
|
||||||
// This thread waits for blockingMappedReduced to finish, so reduce the pool's used thread count
|
state.numMatches += results.size();
|
||||||
// so the blockingMappedReduced can use one more thread, and increase it again afterwards.
|
state.cachedResults << results;
|
||||||
QThreadPool::globalInstance()->releaseThread();
|
state.numFilesSearched += 1;
|
||||||
QtConcurrent::blockingMappedReduced<FileSearchResultList>(m_files->begin(), m_files->end(),
|
if (futureInterface.isProgressUpdateNeeded()
|
||||||
m_searchFunction,
|
|| futureInterface.progressValue() == 0 /*workaround for regression in Qt*/) {
|
||||||
[this](FileSearchResultList &, const FileSearchResultList &results) {
|
if (!state.cachedResults.isEmpty()) {
|
||||||
collect(results);
|
futureInterface.reportResult(state.cachedResults);
|
||||||
},
|
state.cachedResults.clear();
|
||||||
QtConcurrent::OrderedReduce | QtConcurrent::SequentialReduce);
|
|
||||||
QThreadPool::globalInstance()->reserveThread();
|
|
||||||
if (!m_results.isEmpty()) {
|
|
||||||
m_future.reportResult(m_results);
|
|
||||||
m_results.clear();
|
|
||||||
}
|
|
||||||
if (!m_future.isCanceled())
|
|
||||||
m_future.setProgressValueAndText(m_files->currentProgress(), msgFound(m_searchTerm,
|
|
||||||
m_numMatches,
|
|
||||||
m_numFilesSearched));
|
|
||||||
delete m_files;
|
|
||||||
if (m_future.isPaused())
|
|
||||||
m_future.waitForResume();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RunFileSearch::collect(const FileSearchResultList &results)
|
|
||||||
{
|
|
||||||
if (m_future.isCanceled()) {
|
|
||||||
if (!m_canceled) {
|
|
||||||
m_future.setProgressValueAndText(m_files->currentProgress(),
|
|
||||||
msgCanceled(m_searchTerm,
|
|
||||||
m_numMatches,
|
|
||||||
m_numFilesSearched));
|
|
||||||
m_canceled = true;
|
|
||||||
}
|
}
|
||||||
return;
|
futureInterface.setProgressRange(0, state.files->maxProgress());
|
||||||
}
|
futureInterface.setProgressValueAndText(state.files->currentProgress(),
|
||||||
m_numMatches += results.size();
|
msgFound(state.searchTerm,
|
||||||
m_results << results;
|
state.numMatches,
|
||||||
++m_numFilesSearched;
|
state.numFilesSearched));
|
||||||
if (m_future.isProgressUpdateNeeded()
|
|
||||||
|| m_future.progressValue() == 0 /*workaround for regression in Qt*/) {
|
|
||||||
if (!m_results.isEmpty()) {
|
|
||||||
m_future.reportResult(m_results);
|
|
||||||
m_results.clear();
|
|
||||||
}
|
|
||||||
m_future.setProgressRange(0, m_files->maxProgress());
|
|
||||||
m_future.setProgressValueAndText(m_files->currentProgress(), msgFound(m_searchTerm,
|
|
||||||
m_numMatches,
|
|
||||||
m_numFilesSearched));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void runFileSearch(QFutureInterface<FileSearchResultList> &future,
|
void cleanUpFileSearch(QFutureInterface<FileSearchResultList> &futureInterface,
|
||||||
QString searchTerm,
|
SearchState &state)
|
||||||
FileIterator *files,
|
|
||||||
QTextDocument::FindFlags flags,
|
|
||||||
QMap<QString, QString> fileToContentsMap)
|
|
||||||
{
|
{
|
||||||
FileSearch searchFunction(searchTerm, flags, fileToContentsMap, &future);
|
if (!state.cachedResults.isEmpty()) {
|
||||||
RunFileSearch search(future, searchTerm, files, std::bind(&FileSearch::operator(),
|
futureInterface.reportResult(state.cachedResults);
|
||||||
&searchFunction,
|
state.cachedResults.clear();
|
||||||
std::placeholders::_1));
|
}
|
||||||
search.run();
|
if (futureInterface.isCanceled()) {
|
||||||
}
|
futureInterface.setProgressValueAndText(state.files->currentProgress(),
|
||||||
|
msgCanceled(state.searchTerm,
|
||||||
void runFileSearchRegExp(QFutureInterface<FileSearchResultList> &future,
|
state.numMatches,
|
||||||
QString searchTerm,
|
state.numFilesSearched));
|
||||||
FileIterator *files,
|
} else {
|
||||||
QTextDocument::FindFlags flags,
|
futureInterface.setProgressValueAndText(state.files->currentProgress(),
|
||||||
QMap<QString, QString> fileToContentsMap)
|
msgFound(state.searchTerm,
|
||||||
{
|
state.numMatches,
|
||||||
FileSearchRegExp searchFunction(searchTerm, flags, fileToContentsMap, &future);
|
state.numFilesSearched));
|
||||||
RunFileSearch search(future, searchTerm, files, std::bind(&FileSearchRegExp::operator(),
|
}
|
||||||
&searchFunction,
|
delete state.files;
|
||||||
std::placeholders::_1));
|
|
||||||
search.run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
QFuture<FileSearchResultList> Utils::findInFiles(const QString &searchTerm, FileIterator *files,
|
QFuture<FileSearchResultList> Utils::findInFiles(const QString &searchTerm, FileIterator *files,
|
||||||
QTextDocument::FindFlags flags, QMap<QString, QString> fileToContentsMap)
|
QTextDocument::FindFlags flags, QMap<QString, QString> fileToContentsMap)
|
||||||
{
|
{
|
||||||
return QtConcurrent::run<FileSearchResultList, QString, FileIterator *, QTextDocument::FindFlags, QMap<QString, QString> >
|
return mapReduce<FileSearchResultList>(std::cref(*files),
|
||||||
(runFileSearch, searchTerm, files, flags, fileToContentsMap);
|
[searchTerm, files](QFutureInterface<FileSearchResultList> &futureInterface) {
|
||||||
|
return initFileSearch(futureInterface, searchTerm, files);
|
||||||
|
},
|
||||||
|
FileSearch(searchTerm, flags, fileToContentsMap),
|
||||||
|
&collectSearchResults,
|
||||||
|
&cleanUpFileSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
QFuture<FileSearchResultList> Utils::findInFilesRegExp(const QString &searchTerm, FileIterator *files,
|
QFuture<FileSearchResultList> Utils::findInFilesRegExp(const QString &searchTerm, FileIterator *files,
|
||||||
QTextDocument::FindFlags flags, QMap<QString, QString> fileToContentsMap)
|
QTextDocument::FindFlags flags, QMap<QString, QString> fileToContentsMap)
|
||||||
{
|
{
|
||||||
return QtConcurrent::run<FileSearchResultList, QString, FileIterator *, QTextDocument::FindFlags, QMap<QString, QString> >
|
return mapReduce<FileSearchResultList>(std::cref(*files),
|
||||||
(runFileSearchRegExp, searchTerm, files, flags, fileToContentsMap);
|
[searchTerm, files](QFutureInterface<FileSearchResultList> &futureInterface) {
|
||||||
|
return initFileSearch(futureInterface, searchTerm, files);
|
||||||
|
},
|
||||||
|
FileSearchRegExp(searchTerm, flags, fileToContentsMap),
|
||||||
|
&collectSearchResults,
|
||||||
|
&cleanUpFileSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Utils::expandRegExpReplacement(const QString &replaceText, const QStringList &capturedTexts)
|
QString Utils::expandRegExpReplacement(const QString &replaceText, const QStringList &capturedTexts)
|
||||||
@@ -527,12 +479,12 @@ QString matchCaseReplacement(const QString &originalText, const QString &replace
|
|||||||
|
|
||||||
// #pragma mark -- FileIterator
|
// #pragma mark -- FileIterator
|
||||||
|
|
||||||
void FileIterator::next(FileIterator::const_iterator *it)
|
void FileIterator::advance(FileIterator::const_iterator *it) const
|
||||||
{
|
{
|
||||||
if (it->m_index < 0) // == end
|
if (it->m_index < 0) // == end
|
||||||
return;
|
return;
|
||||||
++it->m_index;
|
++it->m_index;
|
||||||
update(it->m_index);
|
const_cast<FileIterator *>(this)->update(it->m_index);
|
||||||
if (it->m_index < currentFileCount()) {
|
if (it->m_index < currentFileCount()) {
|
||||||
it->m_item.filePath = fileAt(it->m_index);
|
it->m_item.filePath = fileAt(it->m_index);
|
||||||
it->m_item.encoding = codecAt(it->m_index);
|
it->m_item.encoding = codecAt(it->m_index);
|
||||||
@@ -543,9 +495,9 @@ void FileIterator::next(FileIterator::const_iterator *it)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileIterator::const_iterator FileIterator::begin()
|
FileIterator::const_iterator FileIterator::begin() const
|
||||||
{
|
{
|
||||||
update(0);
|
const_cast<FileIterator *>(this)->update(0);
|
||||||
if (currentFileCount() == 0)
|
if (currentFileCount() == 0)
|
||||||
return end();
|
return end();
|
||||||
return FileIterator::const_iterator(this,
|
return FileIterator::const_iterator(this,
|
||||||
@@ -553,7 +505,7 @@ FileIterator::const_iterator FileIterator::begin()
|
|||||||
0/*index*/);
|
0/*index*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
FileIterator::const_iterator FileIterator::end()
|
FileIterator::const_iterator FileIterator::end() const
|
||||||
{
|
{
|
||||||
return FileIterator::const_iterator(this, FileIterator::Item(QString(), 0),
|
return FileIterator::const_iterator(this, FileIterator::Item(QString(), 0),
|
||||||
-1/*end*/);
|
-1/*end*/);
|
||||||
|
@@ -56,6 +56,8 @@ public:
|
|||||||
QTextCodec *encoding;
|
QTextCodec *encoding;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef Item value_type;
|
||||||
|
|
||||||
class const_iterator
|
class const_iterator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -65,27 +67,27 @@ public:
|
|||||||
typedef const value_type *pointer;
|
typedef const value_type *pointer;
|
||||||
typedef const value_type &reference;
|
typedef const value_type &reference;
|
||||||
|
|
||||||
const_iterator(FileIterator *parent, Item item, int id)
|
const_iterator(const FileIterator *parent, Item item, int id)
|
||||||
: m_parent(parent), m_item(item), m_index(id)
|
: m_parent(parent), m_item(item), m_index(id)
|
||||||
{}
|
{}
|
||||||
const Item operator*() const { return m_item; }
|
const Item operator*() const { return m_item; }
|
||||||
const Item *operator->() const { return &m_item; }
|
const Item *operator->() const { return &m_item; }
|
||||||
void operator++() { m_parent->next(this); }
|
void operator++() { m_parent->advance(this); }
|
||||||
bool operator==(const const_iterator &other) const
|
bool operator==(const const_iterator &other) const
|
||||||
{
|
{
|
||||||
return m_parent == other.m_parent && m_index == other.m_index;
|
return m_parent == other.m_parent && m_index == other.m_index;
|
||||||
}
|
}
|
||||||
bool operator!=(const const_iterator &other) const { return !operator==(other); }
|
bool operator!=(const const_iterator &other) const { return !operator==(other); }
|
||||||
|
|
||||||
FileIterator *m_parent;
|
const FileIterator *m_parent;
|
||||||
Item m_item;
|
Item m_item;
|
||||||
int m_index; // -1 == end
|
int m_index; // -1 == end
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ~FileIterator() {}
|
virtual ~FileIterator() {}
|
||||||
void next(const_iterator *it);
|
void advance(const_iterator *it) const;
|
||||||
const_iterator begin();
|
const_iterator begin() const;
|
||||||
const_iterator end();
|
const_iterator end() const;
|
||||||
|
|
||||||
virtual int maxProgress() const = 0;
|
virtual int maxProgress() const = 0;
|
||||||
virtual int currentProgress() const = 0;
|
virtual int currentProgress() const = 0;
|
||||||
|
@@ -31,12 +31,18 @@
|
|||||||
#ifndef RUNEXTENSIONS_H
|
#ifndef RUNEXTENSIONS_H
|
||||||
#define RUNEXTENSIONS_H
|
#define RUNEXTENSIONS_H
|
||||||
|
|
||||||
|
#include "qtcassert.h"
|
||||||
|
|
||||||
#include <qrunnable.h>
|
#include <qrunnable.h>
|
||||||
#include <qfuture.h>
|
#include <qfuture.h>
|
||||||
#include <qfutureinterface.h>
|
#include <qfutureinterface.h>
|
||||||
#include <qthreadpool.h>
|
#include <qthreadpool.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@@ -431,4 +437,116 @@ QFuture<T> run(const std::function<void (QFutureInterface<T> &)> &fn)
|
|||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
typename std::vector<std::future<T>>::iterator
|
||||||
|
waitForAny(std::vector<std::future<T>> &futures)
|
||||||
|
{
|
||||||
|
// Wait for any future to have a result ready.
|
||||||
|
// Unfortunately we have to do that in a busy loop because future doesn't have a feature to
|
||||||
|
// wait for any of a set of futures (yet? possibly when_any in C++17).
|
||||||
|
auto end = futures.end();
|
||||||
|
QTC_ASSERT(!futures.empty(), return end);
|
||||||
|
auto futureIterator = futures.begin();
|
||||||
|
forever {
|
||||||
|
if (futureIterator->wait_for(std::chrono::duration<quint64>::zero()) == std::future_status::ready)
|
||||||
|
return futureIterator;
|
||||||
|
++futureIterator;
|
||||||
|
if (futureIterator == end)
|
||||||
|
futureIterator = futures.begin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void swapErase(std::vector<T> &vec, typename std::vector<T>::iterator it)
|
||||||
|
{
|
||||||
|
// efficient erasing by swapping with back element
|
||||||
|
*it = std::move(vec.back());
|
||||||
|
vec.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename MapResult, typename State, typename ReduceResult, typename ReduceFunction>
|
||||||
|
void reduceOne(QFutureInterface<ReduceResult> &futureInterface,
|
||||||
|
std::vector<std::future<MapResult>> &futures,
|
||||||
|
State &state, const ReduceFunction &reduce)
|
||||||
|
{
|
||||||
|
auto futureIterator = waitForAny(futures);
|
||||||
|
if (futureIterator != futures.end()) {
|
||||||
|
reduce(futureInterface, state, futureIterator->get());
|
||||||
|
swapErase(futures, futureIterator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This together with reduceOne can be replaced by std::transformReduce (parallelism TS)
|
||||||
|
// when that becomes widely available in C++ implementations
|
||||||
|
template <typename Container, typename MapFunction, typename State, typename ReduceResult, typename ReduceFunction>
|
||||||
|
void mapReduceLoop(QFutureInterface<ReduceResult> &futureInterface, const Container &container,
|
||||||
|
const MapFunction &map, State &state, const ReduceFunction &reduce)
|
||||||
|
{
|
||||||
|
const unsigned MAX_THREADS = std::thread::hardware_concurrency();
|
||||||
|
using MapResult = typename std::result_of<MapFunction(QFutureInterface<ReduceResult>,typename Container::value_type)>::type;
|
||||||
|
std::vector<std::future<MapResult>> futures;
|
||||||
|
futures.reserve(MAX_THREADS);
|
||||||
|
auto fileIterator = container.begin();
|
||||||
|
auto end = container.end();
|
||||||
|
while (!futureInterface.isCanceled() && (fileIterator != end || futures.size() != 0)) {
|
||||||
|
if (futures.size() >= MAX_THREADS || fileIterator == end) {
|
||||||
|
// We don't want to start a new thread (yet), so try to find a future that is ready and
|
||||||
|
// handle its result.
|
||||||
|
reduceOne(futureInterface, futures, state, reduce);
|
||||||
|
} else { // start a new thread
|
||||||
|
futures.push_back(std::async(std::launch::async,
|
||||||
|
map, futureInterface, *fileIterator));
|
||||||
|
++fileIterator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Container, typename InitFunction, typename MapFunction, typename ReduceResult,
|
||||||
|
typename ReduceFunction, typename CleanUpFunction>
|
||||||
|
void blockingMapReduce(QFutureInterface<ReduceResult> futureInterface, const Container &container,
|
||||||
|
const InitFunction &init, const MapFunction &map,
|
||||||
|
const ReduceFunction &reduce, const CleanUpFunction &cleanup)
|
||||||
|
{
|
||||||
|
auto state = init(futureInterface);
|
||||||
|
futureInterface.reportStarted();
|
||||||
|
mapReduceLoop(futureInterface, container, map, state, reduce);
|
||||||
|
cleanup(futureInterface, state);
|
||||||
|
if (futureInterface.isPaused())
|
||||||
|
futureInterface.waitForResume();
|
||||||
|
futureInterface.reportFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Internal
|
||||||
|
|
||||||
|
template <typename ReduceResult, typename Container, typename InitFunction, typename MapFunction,
|
||||||
|
typename ReduceFunction, typename CleanUpFunction>
|
||||||
|
QFuture<ReduceResult> mapReduce(std::reference_wrapper<Container> containerWrapper,
|
||||||
|
const InitFunction &init, const MapFunction &map,
|
||||||
|
const ReduceFunction &reduce, const CleanUpFunction &cleanup)
|
||||||
|
{
|
||||||
|
auto fi = QFutureInterface<ReduceResult>();
|
||||||
|
QFuture<ReduceResult> future = fi.future();
|
||||||
|
std::thread(Internal::blockingMapReduce<Container, InitFunction, MapFunction, ReduceResult, ReduceFunction, CleanUpFunction>,
|
||||||
|
fi, containerWrapper, init, map, reduce, cleanup).detach();
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ReduceResult, typename Container, typename InitFunction, typename MapFunction,
|
||||||
|
typename ReduceFunction, typename CleanUpFunction>
|
||||||
|
QFuture<ReduceResult> mapReduce(const Container &container, const InitFunction &init, const MapFunction &map,
|
||||||
|
const ReduceFunction &reduce, const CleanUpFunction &cleanup)
|
||||||
|
{
|
||||||
|
auto fi = QFutureInterface<ReduceResult>();
|
||||||
|
QFuture<ReduceResult> future = fi.future();
|
||||||
|
std::thread(Internal::blockingMapReduce<Container, InitFunction, MapFunction, ReduceResult, ReduceFunction, CleanUpFunction>,
|
||||||
|
fi, container, init, map, reduce, cleanup).detach();
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Utils
|
||||||
|
|
||||||
#endif // RUNEXTENSIONS_H
|
#endif // RUNEXTENSIONS_H
|
||||||
|
Reference in New Issue
Block a user