summaryrefslogtreecommitdiffstats
path: root/src/corelib/thread
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2021-04-22 14:33:17 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2021-10-01 12:01:21 +0200
commit9a6c653eaf22f604289a02c844af3bc5880605e1 (patch)
tree137eef7af8da14ad3f62a3580668756a543b8457 /src/corelib/thread
parent2fea6bbe8e822b3fb59a7f74c0165e7a8aeb727a (diff)
Cleanup of qthreadpool
Don't bother overwaiting in waitForDone(), if it was done at one point after it was called we can return true. And do not stop threads recently awakened by a startThread call as they have tasks to do. Make allowing at least one thread regardless of reservation more standard instead of hacked in certain places. Pick-to: 6.2 Change-Id: I304bcdc5822f440d5e72fc33ba2aa1678c9ba0d0 Reviewed-by: David Faure <david.faure@kdab.com>
Diffstat (limited to 'src/corelib/thread')
-rw-r--r--src/corelib/thread/qthreadpool.cpp93
-rw-r--r--src/corelib/thread/qthreadpool_p.h1
2 files changed, 54 insertions, 40 deletions
diff --git a/src/corelib/thread/qthreadpool.cpp b/src/corelib/thread/qthreadpool.cpp
index 4d7d7a2250..96eae6f3f3 100644
--- a/src/corelib/thread/qthreadpool.cpp
+++ b/src/corelib/thread/qthreadpool.cpp
@@ -112,14 +112,13 @@ void QThreadPoolThread::run()
locker.relock();
}
- // if too many threads are active, expire this thread
+ // if too many threads are active, stop working in this one
if (manager->tooManyThreadsActive())
break;
- if (manager->queue.isEmpty()) {
- r = nullptr;
+ // all work is done, time to wait for more
+ if (manager->queue.isEmpty())
break;
- }
QueuePage *page = manager->queue.first();
r = page->pop();
@@ -130,26 +129,32 @@ void QThreadPoolThread::run()
}
} while (true);
- // if too many threads are active, expire this thread
- bool expired = manager->tooManyThreadsActive();
- if (!expired) {
- manager->waitingThreads.enqueue(this);
+ // this thread is about to be deleted, do not wait or expire
+ if (!manager->allThreads.contains(this)) {
registerThreadInactive();
- // wait for work, exiting after the expiry timeout is reached
- runnableReady.wait(locker.mutex(), QDeadlineTimer(manager->expiryTimeout));
- ++manager->activeThreads;
- if (manager->waitingThreads.removeOne(this))
- expired = true;
- if (!manager->allThreads.contains(this)) {
- registerThreadInactive();
- break;
- }
+ return;
}
- if (expired) {
+
+ // if too many threads are active, expire this thread
+ if (manager->tooManyThreadsActive()) {
manager->expiredThreads.enqueue(this);
registerThreadInactive();
- break;
+ return;
+ }
+ manager->waitingThreads.enqueue(this);
+ registerThreadInactive();
+ // wait for work, exiting after the expiry timeout is reached
+ runnableReady.wait(locker.mutex(), QDeadlineTimer(manager->expiryTimeout));
+ // this thread is about to be deleted, do not work or expire
+ if (!manager->allThreads.contains(this)) {
+ Q_ASSERT(manager->queue.isEmpty());
+ return;
+ }
+ if (manager->waitingThreads.removeOne(this)) {
+ manager->expiredThreads.enqueue(this);
+ return;
}
+ ++manager->activeThreads;
}
}
@@ -176,10 +181,10 @@ bool QThreadPoolPrivate::tryStart(QRunnable *task)
}
// can't do anything if we're over the limit
- if (activeThreadCount() >= maxThreadCount())
+ if (areAllThreadsActive())
return false;
- if (waitingThreads.count() > 0) {
+ if (!waitingThreads.isEmpty()) {
// recycle an available thread
enqueueTask(task);
waitingThreads.takeFirst()->runnableReady.wakeOne();
@@ -251,6 +256,12 @@ void QThreadPoolPrivate::tryToStartMoreThreads()
}
}
+bool QThreadPoolPrivate::areAllThreadsActive() const
+{
+ const int activeThreadCount = this->activeThreadCount();
+ return activeThreadCount >= maxThreadCount() && (activeThreadCount - reservedThreads) >= 1;
+}
+
bool QThreadPoolPrivate::tooManyThreadsActive() const
{
const int activeThreadCount = this->activeThreadCount();
@@ -283,18 +294,20 @@ void QThreadPoolPrivate::startThread(QRunnable *runnable)
\internal
Helper function only to be called from waitForDone(int)
+
+ Deletes all current threads.
*/
void QThreadPoolPrivate::reset()
{
// move the contents of the set out so that we can iterate without the lock
- QSet<QThreadPoolThread *> allThreadsCopy;
- allThreadsCopy.swap(allThreads);
+ auto allThreadsCopy = std::exchange(allThreads, {});
expiredThreads.clear();
waitingThreads.clear();
+
mutex.unlock();
for (QThreadPoolThread *thread : qAsConst(allThreadsCopy)) {
- if (!thread->isFinished()) {
+ if (thread->isRunning()) {
thread->runnableReady.wakeAll();
thread->wait();
}
@@ -321,15 +334,13 @@ bool QThreadPoolPrivate::waitForDone(int msecs)
{
QMutexLocker locker(&mutex);
QDeadlineTimer timer(msecs);
- do {
- if (!waitForDone(timer))
- return false;
- reset();
- // More threads can be started during reset(), in that case continue
- // waiting if we still have time left.
- } while ((!queue.isEmpty() || activeThreads) && !timer.hasExpired());
-
- return queue.isEmpty() && activeThreads == 0;
+ if (!waitForDone(timer))
+ return false;
+ reset();
+ // New jobs might have started during reset, but return anyway
+ // as the active thread and task count did reach 0 once, and
+ // race conditions are outside our scope.
+ return true;
}
void QThreadPoolPrivate::clear()
@@ -473,7 +484,10 @@ QThreadPool::QThreadPool(QObject *parent)
*/
QThreadPool::~QThreadPool()
{
+ Q_D(QThreadPool);
waitForDone();
+ Q_ASSERT(d->queue.isEmpty());
+ Q_ASSERT(d->allThreads.isEmpty());
}
/*!
@@ -513,12 +527,8 @@ void QThreadPool::start(QRunnable *runnable, int priority)
Q_D(QThreadPool);
QMutexLocker locker(&d->mutex);
- if (!d->tryStart(runnable)) {
+ if (!d->tryStart(runnable))
d->enqueueTask(runnable, priority);
-
- if (!d->waitingThreads.isEmpty())
- d->waitingThreads.takeFirst()->runnableReady.wakeOne();
- }
}
/*!
@@ -582,7 +592,7 @@ bool QThreadPool::tryStart(std::function<void()> functionToRun)
Q_D(QThreadPool);
QMutexLocker locker(&d->mutex);
- if (!d->allThreads.isEmpty() && d->activeThreadCount() >= d->maxThreadCount())
+ if (!d->allThreads.isEmpty() && d->areAllThreadsActive())
return false;
QRunnable *runnable = QRunnable::create(std::move(functionToRun));
@@ -674,7 +684,10 @@ int QThreadPool::activeThreadCount() const
Once you are done with the thread, call releaseThread() to allow it to be
reused.
- \note This function will always increase the number of active threads.
+ \note Even if reserving maxThreadCount() threads or more, the thread pool
+ will still allow a minimum of one thread.
+
+ \note This function will increase the reported number of active threads.
This means that by using this function, it is possible for
activeThreadCount() to return a value greater than maxThreadCount() .
diff --git a/src/corelib/thread/qthreadpool_p.h b/src/corelib/thread/qthreadpool_p.h
index b21c1b7d41..1b6a65f69d 100644
--- a/src/corelib/thread/qthreadpool_p.h
+++ b/src/corelib/thread/qthreadpool_p.h
@@ -157,6 +157,7 @@ public:
int activeThreadCount() const;
void tryToStartMoreThreads();
+ bool areAllThreadsActive() const;
bool tooManyThreadsActive() const;
int maxThreadCount() const