diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2023-04-11 11:38:37 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2023-05-04 15:05:18 +0200 |
commit | 0e7e1c33965c43932e6c0228acc2f68259533482 (patch) | |
tree | e4c4366c71ae18e7ff631e45bf3f0daff82f7736 | |
parent | 1403b63a5747b8210edaa3da571ae568230dc156 (diff) |
Take move-only functions for the threadpool
We never copy the function so only need it to movable. Moves the
functions to templates using the new QRunnable create version.
[ChangeLog][QtCore][QThreadPool] Methods taking callable functions,
can now take move-only lambdas.
Fixes: QTBUG-112302
Change-Id: I2cb200f0abcf7e0fdbef0457fe2a6176764ad93d
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
-rw-r--r-- | src/corelib/compat/removed_api.cpp | 39 | ||||
-rw-r--r-- | src/corelib/thread/qrunnable.h | 36 | ||||
-rw-r--r-- | src/corelib/thread/qthreadpool.cpp | 48 | ||||
-rw-r--r-- | src/corelib/thread/qthreadpool.h | 33 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp | 20 |
5 files changed, 143 insertions, 33 deletions
diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp index 399d7b15f5..cd1c8691bf 100644 --- a/src/corelib/compat/removed_api.cpp +++ b/src/corelib/compat/removed_api.cpp @@ -524,6 +524,45 @@ qsizetype QString::toUcs4_helper(const ushort *uc, qsizetype length, uint *out) reinterpret_cast<char32_t *>(out)); } +#if QT_CONFIG(thread) + +#include "qthreadpool.h" +#include "private/qthreadpool_p.h" + +void QThreadPool::start(std::function<void()> functionToRun, int priority) +{ + if (!functionToRun) + return; + start(QRunnable::create(std::move(functionToRun)), priority); +} + +bool QThreadPool::tryStart(std::function<void()> functionToRun) +{ + if (!functionToRun) + return false; + + Q_D(QThreadPool); + QMutexLocker locker(&d->mutex); + if (!d->allThreads.isEmpty() && d->areAllThreadsActive()) + return false; + + QRunnable *runnable = QRunnable::create(std::move(functionToRun)); + if (d->tryStart(runnable)) + return true; + delete runnable; + return false; +} + +void QThreadPool::startOnReservedThread(std::function<void()> functionToRun) +{ + if (!functionToRun) + return releaseThread(); + + startOnReservedThread(QRunnable::create(std::move(functionToRun))); +} + +#endif // QT_CONFIG(thread) + #include "qxmlstream.h" QStringView QXmlStreamAttributes::value(const QString &namespaceUri, const QString &name) const diff --git a/src/corelib/thread/qrunnable.h b/src/corelib/thread/qrunnable.h index d8473a7f5f..3b25b67836 100644 --- a/src/corelib/thread/qrunnable.h +++ b/src/corelib/thread/qrunnable.h @@ -4,7 +4,9 @@ #ifndef QRUNNABLE_H #define QRUNNABLE_H -#include <QtCore/qglobal.h> +#include <QtCore/qcompilerdetection.h> +#include <QtCore/qdebug.h> + #include <functional> #include <type_traits> @@ -28,6 +30,8 @@ public: template <typename Callable, if_callable<Callable> = true> static QRunnable *create(Callable &&functionToRun); + static QRunnable *create(std::nullptr_t) = delete; + bool autoDelete() const { return m_autoDelete; } void setAutoDelete(bool autoDelete) { m_autoDelete = autoDelete; } @@ -35,7 +39,7 @@ protected: // Type erasure, to only instantiate a non-virtual class per Callable: class QGenericRunnableHelperBase { - using OpFn = void(*)(const QGenericRunnableHelperBase *); + using OpFn = void(*)(QGenericRunnableHelperBase *); OpFn runFn; OpFn destroyFn; protected: @@ -54,8 +58,8 @@ protected: template <typename UniCallable> QGenericRunnableHelper(UniCallable &&functionToRun) noexcept : QGenericRunnableHelperBase( - [](const QGenericRunnableHelperBase *that) { static_cast<const QGenericRunnableHelper*>(that)->m_functionToRun(); }, - [](const QGenericRunnableHelperBase *that) { delete static_cast<const QGenericRunnableHelper*>(that); }), + [](QGenericRunnableHelperBase *that) { static_cast<QGenericRunnableHelper*>(that)->m_functionToRun(); }, + [](QGenericRunnableHelperBase *that) { delete static_cast<QGenericRunnableHelper*>(that); }), m_functionToRun(std::forward<UniCallable>(functionToRun)) { } @@ -79,9 +83,33 @@ public: } }; +namespace QtPrivate { + +template <typename T> +using is_function_pointer = std::conjunction<std::is_pointer<T>, std::is_function<std::remove_pointer_t<T>>>; +template <typename T> +struct is_std_function : std::false_type {}; +template <typename T> +struct is_std_function<std::function<T>> : std::true_type {}; + +} // namespace QtPrivate + template <typename Callable, QRunnable::if_callable<Callable>> QRunnable *QRunnable::create(Callable &&functionToRun) { + bool is_null = false; + if constexpr(QtPrivate::is_std_function<std::decay_t<Callable>>::value) + is_null = !functionToRun; + + if constexpr(QtPrivate::is_function_pointer<std::decay_t<Callable>>::value) { + const void *functionPtr = reinterpret_cast<void *>(functionToRun); + is_null = !functionPtr; + } + if (is_null) { + qWarning() << "Trying to create null QRunnable. This may stop working."; + return nullptr; + } + return new QGenericRunnable( new QGenericRunnableHelper<std::decay_t<Callable>>( std::forward<Callable>(functionToRun))); diff --git a/src/corelib/thread/qthreadpool.cpp b/src/corelib/thread/qthreadpool.cpp index 51783321b9..a6c2a7e54b 100644 --- a/src/corelib/thread/qthreadpool.cpp +++ b/src/corelib/thread/qthreadpool.cpp @@ -517,6 +517,7 @@ void QThreadPool::start(QRunnable *runnable, int priority) } /*! + \fn template<typename Callable, QRunnable::if_callable<Callable>> void QThreadPool::start(Callable &&callableToRun, int priority) \overload \since 5.15 @@ -524,13 +525,13 @@ void QThreadPool::start(QRunnable *runnable, int priority) make the current thread count exceed maxThreadCount(). In that case, \a functionToRun is added to a run queue instead. The \a priority argument can be used to control the run queue's order of execution. + + \note This function participates in overload resolution only if \c Callable + is a function or function object which can be called with zero arguments. + + \note In Qt version prior to 6.6, this function took std::function<void()>, + and therefore couldn't handle move-only callables. */ -void QThreadPool::start(std::function<void()> functionToRun, int priority) -{ - if (!functionToRun) - return; - start(QRunnable::create(std::move(functionToRun)), priority); -} /*! Attempts to reserve a thread to run \a runnable. @@ -562,6 +563,7 @@ bool QThreadPool::tryStart(QRunnable *runnable) } /*! + \fn template<typename Callable, QRunnable::if_callable<Callable>> bool QThreadPool::tryStart(Callable &&callableToRun) \overload \since 5.15 Attempts to reserve a thread to run \a functionToRun. @@ -569,23 +571,13 @@ bool QThreadPool::tryStart(QRunnable *runnable) If no threads are available at the time of calling, then this function does nothing and returns \c false. Otherwise, \a functionToRun is run immediately using one available thread and this function returns \c true. -*/ -bool QThreadPool::tryStart(std::function<void()> functionToRun) -{ - if (!functionToRun) - return false; - Q_D(QThreadPool); - QMutexLocker locker(&d->mutex); - if (!d->allThreads.isEmpty() && d->areAllThreadsActive()) - return false; + \note This function participates in overload resolution only if \c Callable + is a function or function object which can be called with zero arguments. - QRunnable *runnable = QRunnable::create(std::move(functionToRun)); - if (d->tryStart(runnable)) - return true; - delete runnable; - return false; -} + \note In Qt version prior to 6.6, this function took std::function<void()>, + and therefore couldn't handle move-only callables. +*/ /*! \property QThreadPool::expiryTimeout \brief the thread expiry timeout value in milliseconds. @@ -799,19 +791,19 @@ void QThreadPool::startOnReservedThread(QRunnable *runnable) } /*! + \fn template<typename Callable, QRunnable::if_callable<Callable>> void QThreadPool::startOnReservedThread(Callable &&callableToRun) \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))); -} + \note This function participates in overload resolution only if \c Callable + is a function or function object which can be called with zero arguments. + + \note In Qt version prior to 6.6, this function took std::function<void()>, + and therefore couldn't handle move-only callables. +*/ /*! Waits up to \a msecs milliseconds for all threads to exit and removes all diff --git a/src/corelib/thread/qthreadpool.h b/src/corelib/thread/qthreadpool.h index e6d0326c4c..a097ace14b 100644 --- a/src/corelib/thread/qthreadpool.h +++ b/src/corelib/thread/qthreadpool.h @@ -36,11 +36,22 @@ public: void start(QRunnable *runnable, int priority = 0); bool tryStart(QRunnable *runnable); +#if QT_CORE_REMOVED_SINCE(6, 6) void start(std::function<void()> functionToRun, int priority = 0); bool tryStart(std::function<void()> functionToRun); +#endif void startOnReservedThread(QRunnable *runnable); +#if QT_CORE_REMOVED_SINCE(6, 6) void startOnReservedThread(std::function<void()> functionToRun); +#endif + + template <typename Callable, QRunnable::if_callable<Callable> = true> + void start(Callable &&functionToRun, int priority = 0); + template <typename Callable, QRunnable::if_callable<Callable> = true> + bool tryStart(Callable &&functionToRun); + template <typename Callable, QRunnable::if_callable<Callable> = true> + void startOnReservedThread(Callable &&functionToRun); int expiryTimeout() const; void setExpiryTimeout(int expiryTimeout); @@ -68,6 +79,28 @@ public: [[nodiscard]] bool tryTake(QRunnable *runnable); }; +template <typename Callable, QRunnable::if_callable<Callable>> +void QThreadPool::start(Callable &&functionToRun, int priority) +{ + start(QRunnable::create(std::forward<Callable>(functionToRun)), priority); +} + +template <typename Callable, QRunnable::if_callable<Callable>> +bool QThreadPool::tryStart(Callable &&functionToRun) +{ + QRunnable *runnable = QRunnable::create(std::forward<Callable>(functionToRun)); + if (tryStart(runnable)) + return true; + delete runnable; + return false; +} + +template <typename Callable, QRunnable::if_callable<Callable>> +void QThreadPool::startOnReservedThread(Callable &&functionToRun) +{ + startOnReservedThread(QRunnable::create(std::forward<Callable>(functionToRun))); +} + QT_END_NAMESPACE #endif diff --git a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp index 42f2545574..0c4f11a0dc 100644 --- a/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp +++ b/tests/auto/corelib/thread/qthreadpool/tst_qthreadpool.cpp @@ -87,6 +87,7 @@ private slots: void takeAllAndIncreaseMaxThreadCount(); void waitForDoneAfterTake(); void threadReuse(); + void nullFunctions(); private: QMutex m_functionTestMutex; @@ -187,7 +188,7 @@ void tst_QThreadPool::runFunction3() std::unique_ptr<DeleteCheck> ptr(new DeleteCheck); { TestThreadPool manager; - manager.start(QRunnable::create([my_ptr = std::move(ptr)]() { })); + manager.start([my_ptr = std::move(ptr)]() { }); } QVERIFY(DeleteCheck::s_deleted); } @@ -1465,5 +1466,22 @@ void tst_QThreadPool::threadReuse() } } +void tst_QThreadPool::nullFunctions() +{ + // Note this is not necessarily testing intended behavior, only undocumented behavior. + // If this is changed it should be noted in Behavioral Changes. + FunctionPointer nullFunction = nullptr; + std::function<void()> nullStdFunction(nullptr); + { + TestThreadPool manager; + // should not crash: + manager.start(nullFunction); + manager.start(nullStdFunction); + // should fail (and not leak): + QVERIFY(!manager.tryStart(nullStdFunction)); + QVERIFY(!manager.tryStart(nullFunction)); + } +} + QTEST_MAIN(tst_QThreadPool); #include "tst_qthreadpool.moc" |