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();
|
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
|
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;
|
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);
|
void start(std::function<void()> functionToRun, int priority = 0);
|
||||||
bool tryStart(std::function<void()> functionToRun);
|
bool tryStart(std::function<void()> functionToRun);
|
||||||
|
|
||||||
|
void startOnReservedThread(QRunnable *runnable);
|
||||||
|
void startOnReservedThread(std::function<void()> functionToRun);
|
||||||
|
|
||||||
int expiryTimeout() const;
|
int expiryTimeout() const;
|
||||||
void setExpiryTimeout(int expiryTimeout);
|
void setExpiryTimeout(int expiryTimeout);
|
||||||
|
|
||||||
|
@ -89,6 +89,7 @@ private slots:
|
|||||||
void releaseThread_data();
|
void releaseThread_data();
|
||||||
void releaseThread();
|
void releaseThread();
|
||||||
void reserveAndStart();
|
void reserveAndStart();
|
||||||
|
void reserveAndStart2();
|
||||||
void releaseAndBlock();
|
void releaseAndBlock();
|
||||||
void start();
|
void start();
|
||||||
void tryStart();
|
void tryStart();
|
||||||
@ -729,17 +730,66 @@ void tst_QThreadPool::reserveAndStart() // QTBUG-21051
|
|||||||
// start() will wake up the waiting thread.
|
// start() will wake up the waiting thread.
|
||||||
threadpool->start(&task);
|
threadpool->start(&task);
|
||||||
QTRY_COMPARE(threadpool->activeThreadCount(), 2);
|
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.waitForStarted.acquire();
|
||||||
task.waitBeforeDone.release();
|
task.waitBeforeDone.release();
|
||||||
QTRY_COMPARE(task.count.loadRelaxed(), 2);
|
|
||||||
QTRY_COMPARE(threadpool->activeThreadCount(), 1);
|
QTRY_COMPARE(threadpool->activeThreadCount(), 1);
|
||||||
|
task2.waitForStarted.acquire();
|
||||||
|
task2.waitBeforeDone.release();
|
||||||
|
|
||||||
threadpool->releaseThread();
|
|
||||||
QTRY_COMPARE(threadpool->activeThreadCount(), 0);
|
QTRY_COMPARE(threadpool->activeThreadCount(), 0);
|
||||||
|
|
||||||
threadpool->setMaxThreadCount(savedLimit);
|
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()
|
void tst_QThreadPool::releaseAndBlock()
|
||||||
{
|
{
|
||||||
class WaitingTask : public QRunnable
|
class WaitingTask : public QRunnable
|
||||||
|
Loading…
x
Reference in New Issue
Block a user