summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarc Mutz <marc.mutz@kdab.com>2011-06-30 11:36:03 +0200
committerMarc Mutz <marc.mutz@kdab.com>2014-08-05 18:04:56 +0200
commitc55aec2ed78a5c436a522ad571b10caaffa2295d (patch)
tree2765307ae1bf32fc2c6c237e28eb973231563c72
parenta258ac8feb7f82446debbe2d5d6aa405942ceab4 (diff)
QFutureInterface: allow to work with a QThreadPool != globalInstance()
Background: It is often necessary/advisable to schedule tasks on thread pools != globalInstance(). As Herb Sutter writes in http://www.drdobbs.com/parallel/use-thread-pools-correctly-keep-tasks-sh/216500409 and the Qt Training Material stresses, tasks you schedule on a (global) thread pool should be non-blocking, which currently rules out using any of the QtConcurrent functions for, say, file I/O. Nonetheless it's often convenient to have thread pools also for file I/O, as the thumbnail viewer exercise in the Qt Training Material shows. In this case, you'd use a dedicated thead pool, leaving the global thread pool for CPU-bound tasks. Yet, none of the QtConcurrent functions allow to pick the QThreadPool instance on which to schedule the work created with them. This patch prepares for them to do so. This is the first part of the forward-port of https://qt.gitorious.org/qt/qt/merge_requests/1281. Implement by using a new QThreadPool* member that defaults to nullptr, and adding setThreadPool to set this member, then using it in lieu of QThreadPool::globalInstance() everywhere. I chose to leave m_pool == nullptr to mean globalInstance() to avoid creating the global instance whenever a QFuture is created, even if the future represents the result of a calculation not run on the global thread pool. [ChangeLog][QtCore][QFuture] Can now be used with any QThreadPool, not just globalInstance(). Task-number: QTBUG-17220 Change-Id: I4e1dc18d55cf60141b2fa3d14e2d44a3e9e74858 Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
-rw-r--r--src/corelib/thread/qfutureinterface.cpp14
-rw-r--r--src/corelib/thread/qfutureinterface.h2
-rw-r--r--src/corelib/thread/qfutureinterface_p.h5
-rw-r--r--tests/auto/corelib/thread/qfuture/tst_qfuture.cpp49
4 files changed, 65 insertions, 5 deletions
diff --git a/src/corelib/thread/qfutureinterface.cpp b/src/corelib/thread/qfutureinterface.cpp
index ee3d113196..4f51fd7a0a 100644
--- a/src/corelib/thread/qfutureinterface.cpp
+++ b/src/corelib/thread/qfutureinterface.cpp
@@ -48,7 +48,6 @@
#include <QtCore/qatomic.h>
#include <QtCore/qthread.h>
-#include <QtCore/qthreadpool.h>
#include <private/qthreadpool_p.h>
QT_BEGIN_NAMESPACE
@@ -195,7 +194,7 @@ void QFutureInterfaceBase::waitForResume()
return;
// decrease active thread count since this thread will wait.
- const ThreadPoolThreadReleaser releaser(QThreadPool::globalInstance());
+ const ThreadPoolThreadReleaser releaser(d->pool());
d->pausedWaitCondition.wait(&d->m_mutex);
}
@@ -301,7 +300,7 @@ void QFutureInterfaceBase::waitForResult(int resultIndex)
// To avoid deadlocks and reduce the number of threads used, try to
// run the runnable in the current thread.
- QThreadPool::globalInstance()->d_func()->stealRunnable(d->runnable);
+ d->pool()->d_func()->stealRunnable(d->runnable);
lock.relock();
@@ -322,7 +321,7 @@ void QFutureInterfaceBase::waitForFinished()
lock.unlock();
if (!alreadyFinished) {
- QThreadPool::globalInstance()->d_func()->stealRunnable(d->runnable);
+ d->pool()->d_func()->stealRunnable(d->runnable);
lock.relock();
@@ -364,6 +363,11 @@ void QFutureInterfaceBase::setRunnable(QRunnable *runnable)
d->runnable = runnable;
}
+void QFutureInterfaceBase::setThreadPool(QThreadPool *pool)
+{
+ d->m_pool = pool;
+}
+
void QFutureInterfaceBase::setFilterMode(bool enable)
{
QMutexLocker locker(&d->m_mutex);
@@ -444,7 +448,7 @@ bool QFutureInterfaceBase::derefT() const
QFutureInterfaceBasePrivate::QFutureInterfaceBasePrivate(QFutureInterfaceBase::State initialState)
: refCount(1), m_progressValue(0), m_progressMinimum(0), m_progressMaximum(0),
state(initialState),
- manualProgress(false), m_expectedResultCount(0), runnable(0)
+ manualProgress(false), m_expectedResultCount(0), runnable(0), m_pool(0)
{
progressTime.invalidate();
}
diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h
index e0a59697a1..2b2a211a2f 100644
--- a/src/corelib/thread/qfutureinterface.h
+++ b/src/corelib/thread/qfutureinterface.h
@@ -54,6 +54,7 @@ QT_BEGIN_NAMESPACE
template <typename T> class QFuture;
+class QThreadPool;
class QFutureInterfaceBasePrivate;
class QFutureWatcherBase;
class QFutureWatcherBasePrivate;
@@ -85,6 +86,7 @@ public:
void reportResultsReady(int beginIndex, int endIndex);
void setRunnable(QRunnable *runnable);
+ void setThreadPool(QThreadPool *pool);
void setFilterMode(bool enable);
void setProgressRange(int minimum, int maximum);
int progressMinimum() const;
diff --git a/src/corelib/thread/qfutureinterface_p.h b/src/corelib/thread/qfutureinterface_p.h
index 8b38ebfc66..7d9fc850fa 100644
--- a/src/corelib/thread/qfutureinterface_p.h
+++ b/src/corelib/thread/qfutureinterface_p.h
@@ -58,6 +58,7 @@
#include <QtCore/qlist.h>
#include <QtCore/qwaitcondition.h>
#include <QtCore/qrunnable.h>
+#include <QtCore/qthreadpool.h>
QT_BEGIN_NAMESPACE
@@ -169,6 +170,10 @@ public:
QtPrivate::ExceptionStore m_exceptionStore;
QString m_progressText;
QRunnable *runnable;
+ QThreadPool *m_pool;
+
+ inline QThreadPool *pool() const
+ { return m_pool ? m_pool : QThreadPool::globalInstance(); }
// Internal functions that does not change the mutex state.
// The mutex must be locked when calling these.
diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
index c86befdf1b..0ab1b57e31 100644
--- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
+++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
@@ -47,6 +47,7 @@
#include <qfuture.h>
#include <qfuturewatcher.h>
#include <qresultstore.h>
+#include <qthreadpool.h>
#include <qexception.h>
#include <private/qfutureinterface_p.h>
@@ -80,6 +81,7 @@ private slots:
void exceptions();
void nestedExceptions();
#endif
+ void nonGlobalThreadPool();
};
void tst_QFuture::resultStore()
@@ -1444,6 +1446,53 @@ void tst_QFuture::nestedExceptions()
QVERIFY(MyClass::caught);
}
+void tst_QFuture::nonGlobalThreadPool()
+{
+ static Q_CONSTEXPR int Answer = 42;
+
+ struct UselessTask : QRunnable, QFutureInterface<int>
+ {
+ QFuture<int> start(QThreadPool *pool)
+ {
+ setRunnable(this);
+ setThreadPool(pool);
+ reportStarted();
+ QFuture<int> f = future();
+ pool->start(this);
+ return f;
+ }
+
+ void run() Q_DECL_OVERRIDE
+ {
+ const int ms = 100 + (qrand() % 100 - 100/2);
+ QThread::msleep(ms);
+ reportResult(Answer);
+ reportFinished();
+ }
+ };
+
+ QThreadPool pool;
+
+ const int numTasks = QThread::idealThreadCount();
+
+ QVector<QFuture<int> > futures;
+ futures.reserve(numTasks);
+
+ for (int i = 0; i < numTasks; ++i)
+ futures.push_back((new UselessTask)->start(&pool));
+
+ QVERIFY(!pool.waitForDone(0)); // pool is busy (meaning our tasks did end up executing there)
+
+ QVERIFY(pool.waitForDone(10000)); // max sleep time in UselessTask::run is 150ms, so 10s should be enough
+ // (and the call returns as soon as all tasks finished anyway, so the
+ // maximum wait time only matters when the test fails)
+
+ Q_FOREACH (const QFuture<int> &future, futures) {
+ QVERIFY(future.isFinished());
+ QCOMPARE(future.result(), Answer);
+ }
+}
+
#endif // QT_NO_EXCEPTIONS
QTEST_MAIN(tst_QFuture)