summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2017-04-03 13:52:20 +0100
committerGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2017-04-24 15:24:22 +0000
commit597d4ff7962c0add87e4b93da4c366503d11aff5 (patch)
tree7d90570c83e914efab1ff9adf40e505e2c9611a6
parent711e94916ad4b97128849072caf977c13152c64c (diff)
QThread: add static create function
In the spirit of std::thread, which takes a function to call and its parameters, and runs it in a new thread. Since the user might want to connect to signals, move QObjects into the new thread, etc., the new thread is not immediately started. Although technically all of this _should_ be implementable in pure C++11, there is nothing in the Standard to help us not reinvent all the plumbing: packing the decay'd parameters, storing them, invoking the function over the parameters (honoring INVOKE/std::invoke semantics). std::function does not do the job, as it's copiable and therefore does not support move-only functors; std::bind does not have INVOKE semantics. I certainly do not want to reimplement all the required facilities inside of Qt. Therefore, the full blown implementation requires C++17 (std::invoke). In order to make this useful also in pre-C++17, there are two additional implementations (C++11 and C++14) that support just a callable, without any arguments passed to it. The C++11 implementation makes use of a class to store and call the callable (even move-only ones); basically, it's what a closure type for a C++14 lambda would look like. An alternative implementation could've used some of the existing facilities inside QObject::connect implementation that store a functor (for the connect() overload connecting to free functions), namely: the QtPrivate::QFunctorSlotObject class. However: * QFunctorSlotObject does not support move-only callables (see QTBUG-60339); * QFunctorSlotObject itself is not a callable (apparently by design), and requires to be wrapped in a lambda that calls call() on it; * the moment QTBUG-60339 is solved, we'd need the same handwritten closure to keep QFunctorSlotObject working with move-only callabes. So: just use the handwritten one. The C++14 implementation is a simplified version of the C++11 one, actually using a generalized lambda capture (corresponding to the handwritten C++11 closure type). All three implementations use std::async (with a deferred launch policy, a nice use case for it!) under the hood. It's certainly an overkill for our use case, as we don't need the std::future, but at least std::async does all the plumbing for us. [ChangeLog][QtCore][QThread] Added the QThread::create function. Change-Id: I339d0be6f689df7d56766839baebda0aa2f7e94c Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r--src/corelib/thread/qthread.cpp45
-rw-r--r--src/corelib/thread/qthread.h120
-rw-r--r--tests/auto/corelib/thread/qthread/qthread.pro2
-rw-r--r--tests/auto/corelib/thread/qthread/tst_qthread.cpp259
4 files changed, 426 insertions, 0 deletions
diff --git a/src/corelib/thread/qthread.cpp b/src/corelib/thread/qthread.cpp
index 0828400733..c42e120978 100644
--- a/src/corelib/thread/qthread.cpp
+++ b/src/corelib/thread/qthread.cpp
@@ -880,6 +880,51 @@ bool QThread::isInterruptionRequested() const
return d->interruptionRequested;
}
+/*
+ \fn template <typename Function, typename Args...> static QThread *QThread::create(Function &&f, Args &&... args)
+ \since 5.10
+
+ Creates a new QThread object that will execute the function \a f with the
+ arguments \a args.
+
+ The new thread is not started -- it must be started by an explicit call
+ to start(). This allows you to connect to its signals, move QObjects
+ to the thread, choose the new thread's priority and so on. The function
+ \a f will be called in the new thread.
+
+ Returns the newly created QThread instance.
+
+ \note the caller acquires ownership of the returned QThread instance.
+
+ \note this function is only available when using C++17.
+
+ \warning do not call start() on the returned QThread instance more than once;
+ doing so will result in undefined behavior.
+
+ \sa start()
+*/
+
+/*
+ \fn template <typename Function> static QThread *QThread::create(Function &&f)
+ \since 5.10
+
+ Creates a new QThread object that will execute the function \a f.
+
+ The new thread is not started -- it must be started by an explicit call
+ to start(). This allows you to connect to its signals, move QObjects
+ to the thread, choose the new thread's priority and so on. The function
+ \a f will be called in the new thread.
+
+ Returns the newly created QThread instance.
+
+ \note the caller acquires ownership of the returned QThread instance.
+
+ \warning do not call start() on the returned QThread instance more than once;
+ doing so will result in undefined behavior.
+
+ \sa start()
+*/
+
/*!
\class QDaemonThread
\since 5.5
diff --git a/src/corelib/thread/qthread.h b/src/corelib/thread/qthread.h
index 45786537e2..801bbfe02a 100644
--- a/src/corelib/thread/qthread.h
+++ b/src/corelib/thread/qthread.h
@@ -42,6 +42,23 @@
#include <QtCore/qobject.h>
+// The implementation of QThread::create uses various C++14/C++17 facilities;
+// we must check for their presence. For std::async (used in all codepaths)
+// there is no SG10 feature macro; just test for the header presence.
+// For the C++17 codepath do some more throughout checks for std::invoke and
+// C++14 lambdas availability.
+#if QT_HAS_INCLUDE(<future>)
+# define QTHREAD_HAS_CREATE
+# include <future> // for std::async
+# include <functional> // for std::invoke; no guard needed as it's a C++98 header
+
+# if defined(__cpp_lib_invoke) && __cpp_lib_invoke >= 201411 \
+ && defined(__cpp_init_captures) && __cpp_init_captures >= 201304 \
+ && defined(__cpp_generic_lambdas) && __cpp_generic_lambdas >= 201304
+# define QTHREAD_HAS_VARIADIC_CREATE
+# endif
+#endif
+
#include <limits.h>
QT_BEGIN_NAMESPACE
@@ -98,6 +115,16 @@ public:
bool event(QEvent *event) Q_DECL_OVERRIDE;
int loopLevel() const;
+#ifdef QTHREAD_HAS_CREATE
+#ifdef QTHREAD_HAS_VARIADIC_CREATE
+ template <typename Function, typename... Args>
+ static QThread *create(Function &&f, Args &&... args);
+#else
+ template <typename Function>
+ static QThread *create(Function &&f);
+#endif
+#endif
+
public Q_SLOTS:
void start(Priority = InheritPriority);
void terminate();
@@ -131,6 +158,99 @@ private:
friend class QThreadData;
};
+#ifdef QTHREAD_HAS_CREATE
+namespace QtPrivate {
+
+class QThreadCreateThread : public QThread
+{
+public:
+#if defined(QTHREAD_HAS_VARIADIC_CREATE)
+ // C++17: std::thread's constructor complying call
+ template <typename Function, typename... Args>
+ explicit QThreadCreateThread(Function &&f, Args &&... args)
+ : m_future(std::async(std::launch::deferred,
+ [f = static_cast<typename std::decay<Function>::type>(std::forward<Function>(f))](auto &&... args) mutable -> void
+ {
+ (void)std::invoke(std::move(f), std::forward<decltype(args)>(args)...);
+ }, std::forward<Args>(args)...))
+ {
+ }
+#elif defined(__cpp_init_captures) && __cpp_init_captures >= 201304
+ // C++14: implementation for just one callable
+ template <typename Function>
+ explicit QThreadCreateThread(Function &&f)
+ : m_future(std::async(std::launch::deferred,
+ [f = static_cast<typename std::decay<Function>::type>(std::forward<Function>(f))]() mutable -> void
+ {
+ (void)f();
+ }))
+ {
+ }
+#else
+private:
+ // C++11: same as C++14, but with a workaround for not having generalized lambda captures
+ template <typename Function>
+ struct Callable
+ {
+ explicit Callable(Function &&f)
+ : m_function(std::forward<Function>(f))
+ {
+ }
+
+#if defined(Q_COMPILER_DEFAULT_MEMBERS) && defined(Q_COMPILER_DELETE_MEMBERS)
+ // Apply the same semantics of a lambda closure type w.r.t. the special
+ // member functions, if possible: delete the copy assignment operator,
+ // bring back all the others as per the RO5 (cf. ยง8.1.5.1/11 [expr.prim.lambda.closure])
+ ~Callable() = default;
+ Callable(const Callable &) = default;
+ Callable(Callable &&) = default;
+ Callable &operator=(const Callable &) = delete;
+ Callable &operator=(Callable &&) = default;
+#endif
+
+ void operator()()
+ {
+ (void)m_function();
+ }
+
+ typename std::decay<Function>::type m_function;
+ };
+
+public:
+ template <typename Function>
+ explicit QThreadCreateThread(Function &&f)
+ : m_future(std::async(std::launch::deferred, Callable<Function>(std::forward<Function>(f))))
+ {
+ }
+#endif // QTHREAD_HAS_VARIADIC_CREATE
+
+private:
+ void run() override
+ {
+ m_future.get();
+ }
+
+ std::future<void> m_future;
+};
+
+} // namespace QtPrivate
+
+#ifdef QTHREAD_HAS_VARIADIC_CREATE
+template <typename Function, typename... Args>
+QThread *QThread::create(Function &&f, Args &&... args)
+{
+ return new QtPrivate::QThreadCreateThread(std::forward<Function>(f), std::forward<Args>(args)...);
+}
+#else
+template <typename Function>
+QThread *QThread::create(Function &&f)
+{
+ return new QtPrivate::QThreadCreateThread(std::forward<Function>(f));
+}
+#endif // QTHREAD_HAS_VARIADIC_CREATE
+
+#endif // QTHREAD_HAS_CREATE
+
#else // QT_NO_THREAD
class Q_CORE_EXPORT QThread : public QObject
diff --git a/tests/auto/corelib/thread/qthread/qthread.pro b/tests/auto/corelib/thread/qthread/qthread.pro
index 18d867ecef..381f6c9e45 100644
--- a/tests/auto/corelib/thread/qthread/qthread.pro
+++ b/tests/auto/corelib/thread/qthread/qthread.pro
@@ -2,3 +2,5 @@ CONFIG += testcase
TARGET = tst_qthread
QT = core testlib
SOURCES = tst_qthread.cpp
+qtConfig(c++14):CONFIG += c++14
+qtConfig(c++1z):CONFIG += c++1z
diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp
index 0efbc5d01e..b107cf4b60 100644
--- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp
+++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp
@@ -47,6 +47,10 @@
#endif
#endif
+#ifndef QT_NO_EXCEPTIONS
+#include <exception>
+#endif
+
class tst_QThread : public QObject
{
Q_OBJECT
@@ -98,6 +102,8 @@ private slots:
void stressTest();
void quitLock();
+
+ void create();
};
enum { one_minute = 60 * 1000, five_minutes = 5 * one_minute };
@@ -1325,6 +1331,259 @@ void tst_QThread::quitLock()
QVERIFY(exitThreadCalled);
}
+void tst_QThread::create()
+{
+#ifndef QTHREAD_HAS_CREATE
+ QSKIP("This test requires QThread::create");
+#else
+ {
+ const auto &function = [](){};
+ QScopedPointer<QThread> thread(QThread::create(function));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+ }
+
+ {
+ // no side effects before starting
+ int i = 0;
+ const auto &function = [&i]() { i = 42; };
+ QScopedPointer<QThread> thread(QThread::create(function));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ QCOMPARE(i, 0);
+ thread->start();
+ QVERIFY(thread->wait());
+ QCOMPARE(i, 42);
+ }
+
+ {
+ // control thread progress
+ QSemaphore semaphore1;
+ QSemaphore semaphore2;
+
+ const auto &function = [&semaphore1, &semaphore2]() -> void
+ {
+ semaphore1.acquire();
+ semaphore2.release();
+ };
+
+ QScopedPointer<QThread> thread(QThread::create(function));
+
+ QVERIFY(thread);
+ thread->start();
+ QTRY_VERIFY(thread->isRunning());
+ semaphore1.release();
+ semaphore2.acquire();
+ QVERIFY(thread->wait());
+ QVERIFY(!thread->isRunning());
+ }
+
+ {
+ // ignore return values
+ const auto &function = []() { return 42; };
+ QScopedPointer<QThread> thread(QThread::create(function));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+ }
+
+ {
+ // return value of create
+ QScopedPointer<QThread> thread;
+ QSemaphore s;
+ const auto &function = [&thread, &s]() -> void
+ {
+ s.acquire();
+ QCOMPARE(thread.data(), QThread::currentThread());
+ };
+
+ thread.reset(QThread::create(function));
+ QVERIFY(thread);
+ thread->start();
+ QTRY_VERIFY(thread->isRunning());
+ s.release();
+ QVERIFY(thread->wait());
+ }
+
+ {
+ // move-only parameters
+ struct MoveOnlyValue {
+ explicit MoveOnlyValue(int v) : v(v) {}
+ ~MoveOnlyValue() = default;
+ MoveOnlyValue(const MoveOnlyValue &) = delete;
+ MoveOnlyValue(MoveOnlyValue &&) = default;
+ MoveOnlyValue &operator=(const MoveOnlyValue &) = delete;
+ MoveOnlyValue &operator=(MoveOnlyValue &&) = default;
+ int v;
+ };
+
+ struct MoveOnlyFunctor {
+ explicit MoveOnlyFunctor(int *i) : i(i) {}
+ ~MoveOnlyFunctor() = default;
+ MoveOnlyFunctor(const MoveOnlyFunctor &) = delete;
+ MoveOnlyFunctor(MoveOnlyFunctor &&) = default;
+ MoveOnlyFunctor &operator=(const MoveOnlyFunctor &) = delete;
+ MoveOnlyFunctor &operator=(MoveOnlyFunctor &&) = default;
+ int operator()() { return (*i = 42); }
+ int *i;
+ };
+
+ {
+ int i = 0;
+ MoveOnlyFunctor f(&i);
+ QScopedPointer<QThread> thread(QThread::create(std::move(f)));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+ QCOMPARE(i, 42);
+ }
+
+#if defined(__cpp_init_captures) && __cpp_init_captures >= 201304
+ {
+ int i = 0;
+ MoveOnlyValue mo(123);
+ auto moveOnlyFunction = [&i, mo = std::move(mo)]() { i = mo.v; };
+ QScopedPointer<QThread> thread(QThread::create(std::move(moveOnlyFunction)));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+ QCOMPARE(i, 123);
+ }
+#endif // __cpp_init_captures
+
+#ifdef QTHREAD_HAS_VARIADIC_CREATE
+ {
+ int i = 0;
+ const auto &function = [&i](MoveOnlyValue &&mo) { i = mo.v; };
+ QScopedPointer<QThread> thread(QThread::create(function, MoveOnlyValue(123)));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+ QCOMPARE(i, 123);
+ }
+
+ {
+ int i = 0;
+ const auto &function = [&i](MoveOnlyValue &&mo) { i = mo.v; };
+ MoveOnlyValue mo(-1);
+ QScopedPointer<QThread> thread(QThread::create(function, std::move(mo)));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+ QCOMPARE(i, -1);
+ }
+#endif // QTHREAD_HAS_VARIADIC_CREATE
+ }
+
+#ifdef QTHREAD_HAS_VARIADIC_CREATE
+ {
+ // simple parameter passing
+ int i = 0;
+ const auto &function = [&i](int j, int k) { i = j * k; };
+ QScopedPointer<QThread> thread(QThread::create(function, 3, 4));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ QCOMPARE(i, 0);
+ thread->start();
+ QVERIFY(thread->wait());
+ QCOMPARE(i, 12);
+ }
+
+ {
+ // ignore return values (with parameters)
+ const auto &function = [](double d) { return d * 2.0; };
+ QScopedPointer<QThread> thread(QThread::create(function, 3.14));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+ }
+
+ {
+ // handling of pointers to member functions, std::ref, etc.
+ struct S {
+ S() : v(0) {}
+ void doSomething() { ++v; }
+ int v;
+ };
+
+ S object;
+
+ QCOMPARE(object.v, 0);
+
+ QScopedPointer<QThread> thread;
+ thread.reset(QThread::create(&S::doSomething, object));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+
+ QCOMPARE(object.v, 0); // a copy was passed, this should still be 0
+
+ thread.reset(QThread::create(&S::doSomething, std::ref(object)));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+
+ QCOMPARE(object.v, 1);
+
+ thread.reset(QThread::create(&S::doSomething, &object));
+ QVERIFY(thread);
+ QVERIFY(!thread->isRunning());
+ thread->start();
+ QVERIFY(thread->wait());
+
+ QCOMPARE(object.v, 2);
+ }
+
+ {
+ // std::ref into ordinary reference
+ int i = 42;
+ const auto &function = [](int &i) { i *= 2; };
+ QScopedPointer<QThread> thread(QThread::create(function, std::ref(i)));
+ QVERIFY(thread);
+ thread->start();
+ QVERIFY(thread->wait());
+ QCOMPARE(i, 84);
+ }
+
+#ifndef QT_NO_EXCEPTIONS
+ {
+ // exceptions when copying/decaying the arguments are thrown at build side and won't terminate
+ class ThreadException : public std::exception
+ {
+ };
+
+ struct ThrowWhenCopying
+ {
+ ThrowWhenCopying() = default;
+ ThrowWhenCopying(const ThrowWhenCopying &)
+ {
+ throw ThreadException();
+ }
+ ~ThrowWhenCopying() = default;
+ ThrowWhenCopying &operator=(const ThrowWhenCopying &) = default;
+ };
+
+ const auto &function = [](const ThrowWhenCopying &){};
+ QScopedPointer<QThread> thread;
+ ThrowWhenCopying t;
+ QVERIFY_EXCEPTION_THROWN(thread.reset(QThread::create(function, t)), ThreadException);
+ QVERIFY(!thread);
+ }
+#endif // QT_NO_EXCEPTIONS
+#endif // QTHREAD_HAS_VARIADIC_CREATE
+#endif // QTHREAD_HAS_CREATE
+}
+
class StopableJob : public QObject
{
Q_OBJECT