Add a safer way to use QThreadPool::reserveThread
Add startOnReservedThread that specifically releases a reserved thread and uses it atomically for a given task. This can make a positive number of reserved threads work. Change-Id: I4bd1dced24bb46fcb365f12cbc9c7905dc66cdf1 Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
parent
2c3617dfcb
commit
733923c81c
@ -770,6 +770,57 @@ void QThreadPool::releaseThread()
|
||||
d->tryToStartMoreThreads();
|
||||
}
|
||||
|
||||
/*!
|
||||
Releases a thread previously reserved with reserveThread() and uses it
|
||||
to run \a runnable.
|
||||
|
||||
Note that the thread pool takes ownership of the \a runnable if
|
||||
\l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c true,
|
||||
and the \a runnable will be deleted automatically by the thread
|
||||
pool after the \l{QRunnable::run()}{runnable->run()} returns. If
|
||||
\l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c false,
|
||||
ownership of \a runnable remains with the caller. Note that
|
||||
changing the auto-deletion on \a runnable after calling this
|
||||
functions results in undefined behavior.
|
||||
|
||||
\note Calling this when no threads are reserved results in
|
||||
undefined behavior.
|
||||
|
||||
\since 6.3
|
||||
\sa reserveThread(), start()
|
||||
*/
|
||||
void QThreadPool::startOnReservedThread(QRunnable *runnable)
|
||||
{
|
||||
if (!runnable)
|
||||
return releaseThread();
|
||||
|
||||
Q_D(QThreadPool);
|
||||
QMutexLocker locker(&d->mutex);
|
||||
Q_ASSERT(d->reservedThreads > 0);
|
||||
--d->reservedThreads;
|
||||
|
||||
if (!d->tryStart(runnable)) {
|
||||
// This can only happen if we reserved max threads,
|
||||
// and something took the one minimum thread.
|
||||
d->enqueueTask(runnable, INT_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\overload
|
||||
\since 6.3
|
||||
|
||||
Releases a thread previously reserved with reserveThread() and uses it
|
||||
to run \a functionToRun.
|
||||
*/
|
||||
void QThreadPool::startOnReservedThread(std::function<void()> functionToRun)
|
||||
{
|
||||
if (!functionToRun)
|
||||
return releaseThread();
|
||||
|
||||
startOnReservedThread(QRunnable::create(std::move(functionToRun)));
|
||||
}
|
||||
|
||||
/*!
|
||||
Waits up to \a msecs milliseconds for all threads to exit and removes all
|
||||
threads from the thread pool. Returns \c true if all threads were removed;
|
||||
|
@ -75,6 +75,9 @@ public:
|
||||
void start(std::function<void()> functionToRun, int priority = 0);
|
||||
bool tryStart(std::function<void()> functionToRun);
|
||||
|
||||
void startOnReservedThread(QRunnable *runnable);
|
||||
void startOnReservedThread(std::function<void()> functionToRun);
|
||||
|
||||
int expiryTimeout() const;
|
||||
void setExpiryTimeout(int expiryTimeout);
|
||||
|
||||
|
@ -89,6 +89,7 @@ private slots:
|
||||
void releaseThread_data();
|
||||
void releaseThread();
|
||||
void reserveAndStart();
|
||||
void reserveAndStart2();
|
||||
void releaseAndBlock();
|
||||
void start();
|
||||
void tryStart();
|
||||
@ -729,17 +730,66 @@ void tst_QThreadPool::reserveAndStart() // QTBUG-21051
|
||||
// start() will wake up the waiting thread.
|
||||
threadpool->start(&task);
|
||||
QTRY_COMPARE(threadpool->activeThreadCount(), 2);
|
||||
QTRY_COMPARE(task.count.loadRelaxed(), 2);
|
||||
WaitingTask task2;
|
||||
// startOnReservedThread() will try to take the reserved task, but end up waiting instead
|
||||
threadpool->startOnReservedThread(&task2);
|
||||
QTRY_COMPARE(threadpool->activeThreadCount(), 1);
|
||||
task.waitForStarted.acquire();
|
||||
task.waitBeforeDone.release();
|
||||
QTRY_COMPARE(task.count.loadRelaxed(), 2);
|
||||
QTRY_COMPARE(threadpool->activeThreadCount(), 1);
|
||||
task2.waitForStarted.acquire();
|
||||
task2.waitBeforeDone.release();
|
||||
|
||||
threadpool->releaseThread();
|
||||
QTRY_COMPARE(threadpool->activeThreadCount(), 0);
|
||||
|
||||
threadpool->setMaxThreadCount(savedLimit);
|
||||
}
|
||||
|
||||
void tst_QThreadPool::reserveAndStart2()
|
||||
{
|
||||
class WaitingTask : public QRunnable
|
||||
{
|
||||
public:
|
||||
QSemaphore waitBeforeDone;
|
||||
|
||||
WaitingTask() { setAutoDelete(false); }
|
||||
|
||||
void run() override
|
||||
{
|
||||
waitBeforeDone.acquire();
|
||||
}
|
||||
};
|
||||
|
||||
// Set up
|
||||
QThreadPool *threadpool = QThreadPool::globalInstance();
|
||||
int savedLimit = threadpool->maxThreadCount();
|
||||
threadpool->setMaxThreadCount(2);
|
||||
|
||||
// reserve
|
||||
threadpool->reserveThread();
|
||||
|
||||
// start two task, to get a running thread and one queued
|
||||
WaitingTask task1, task2, task3;
|
||||
threadpool->start(&task1);
|
||||
// one running thread, one reserved:
|
||||
QCOMPARE(threadpool->activeThreadCount(), 2);
|
||||
// task2 starts queued
|
||||
threadpool->start(&task2);
|
||||
QCOMPARE(threadpool->activeThreadCount(), 2);
|
||||
// startOnReservedThread() will take the reserved thread however, bypassing the queue
|
||||
threadpool->startOnReservedThread(&task3);
|
||||
// two running threads, none reserved:
|
||||
QCOMPARE(threadpool->activeThreadCount(), 2);
|
||||
task3.waitBeforeDone.release();
|
||||
// task3 can finish even if all other tasks are blocking
|
||||
// then task2 will use the previously reserved thread
|
||||
task2.waitBeforeDone.release();
|
||||
QTRY_COMPARE(threadpool->activeThreadCount(), 1);
|
||||
task1.waitBeforeDone.release();
|
||||
QTRY_COMPARE(threadpool->activeThreadCount(), 0);
|
||||
}
|
||||
|
||||
void tst_QThreadPool::releaseAndBlock()
|
||||
{
|
||||
class WaitingTask : public QRunnable
|
||||
|
Loading…
Reference in New Issue
Block a user