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>
This commit is contained in:
Giuseppe D'Angelo 2017-04-03 13:52:20 +01:00
parent 711e94916a
commit 597d4ff796
4 changed files with 426 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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