From 597d4ff7962c0add87e4b93da4c366503d11aff5 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo Date: Mon, 3 Apr 2017 13:52:20 +0100 Subject: 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 --- src/corelib/thread/qthread.cpp | 45 ++++ src/corelib/thread/qthread.h | 120 ++++++++++ tests/auto/corelib/thread/qthread/qthread.pro | 2 + tests/auto/corelib/thread/qthread/tst_qthread.cpp | 259 ++++++++++++++++++++++ 4 files changed, 426 insertions(+) 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 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 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 +// 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() +# define QTHREAD_HAS_CREATE +# include // for std::async +# include // 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 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 + static QThread *create(Function &&f, Args &&... args); +#else + template + 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 + explicit QThreadCreateThread(Function &&f, Args &&... args) + : m_future(std::async(std::launch::deferred, + [f = static_cast::type>(std::forward(f))](auto &&... args) mutable -> void + { + (void)std::invoke(std::move(f), std::forward(args)...); + }, std::forward(args)...)) + { + } +#elif defined(__cpp_init_captures) && __cpp_init_captures >= 201304 + // C++14: implementation for just one callable + template + explicit QThreadCreateThread(Function &&f) + : m_future(std::async(std::launch::deferred, + [f = static_cast::type>(std::forward(f))]() mutable -> void + { + (void)f(); + })) + { + } +#else +private: + // C++11: same as C++14, but with a workaround for not having generalized lambda captures + template + struct Callable + { + explicit Callable(Function &&f) + : m_function(std::forward(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::type m_function; + }; + +public: + template + explicit QThreadCreateThread(Function &&f) + : m_future(std::async(std::launch::deferred, Callable(std::forward(f)))) + { + } +#endif // QTHREAD_HAS_VARIADIC_CREATE + +private: + void run() override + { + m_future.get(); + } + + std::future m_future; +}; + +} // namespace QtPrivate + +#ifdef QTHREAD_HAS_VARIADIC_CREATE +template +QThread *QThread::create(Function &&f, Args &&... args) +{ + return new QtPrivate::QThreadCreateThread(std::forward(f), std::forward(args)...); +} +#else +template +QThread *QThread::create(Function &&f) +{ + return new QtPrivate::QThreadCreateThread(std::forward(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 +#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 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 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 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 thread(QThread::create(function)); + QVERIFY(thread); + QVERIFY(!thread->isRunning()); + thread->start(); + QVERIFY(thread->wait()); + } + + { + // return value of create + QScopedPointer 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 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 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 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 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 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 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 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 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 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 -- cgit v1.2.3