The applicable Qt APIs depend on what the functionality of the thread protected method is. Let's look at the circumstances from the most general to the most specific.
Signals
Signal bodies are generated by the moc tool and are thread safe.
Corollary 1: All connected slots / functors with a direct connection must be thread safe : otherwise, the signal contract is terminated . While the system of signal slots allows you to decouple the code, a specific case of direct connection of the leak of signal requirements to the connected code!
Corollary 2: Direct connections are more rigid than automatic connections.
Performing work in an object stream
The most common approach is to ensure that the method always runs in the thread() object. This makes it thread safe with respect to the object, but, of course, the use of any other objects from this method should also be performed with the stream.
In general, an unsafe method can only be called from a thread() object:
void MyObject::method() { Q_ASSERT(thread() == QThread::currentThread()); ... }
The special case of a threadlike object requires some caution. When the flow ends, the object becomes threadlike. However, just because an object has no threads does not make all of its methods thread safe. It would be preferable to select one thread to “own” such objects in order to ensure thread safety. Such a stream may be the main stream:
Q_ASSERT(QThread::currentThread() == (thread() ? thread() : qApp()->thread()));
Our task is to fulfill this statement. Here's how:
Use thread safe signals.
Since the signals are thread safe, we can make our method a signal and implement it in the slot:
class MyObject : public QObject { Q_OBJECT int x; void method_impl(int a) { x = a; } Q_SIGNAL void method_signal(int); public: void method(int a) { method_signal(a); } MyObject(QObject * parent = nullptr) : QObject{parent} { connect(this, &MyObject::method, this, &MyObject::method_impl); } };
This approach works to defend the statement, but it is verbose and performs additional dynamic allocation for each argument (at least with Qt 5.7).
Sending a call in a functor to an object stream.
There are many ways to do this ; let it be the one that performs the minimum number of dynamic distributions: in most cases, exactly one.
We can wrap the method call in a functor and make sure that it has performed thread safety:
void method1(int val) { if (!isSafe(this)) return postCall(this, [=]{ method1(val); }); qDebug() << __FUNCTION__; num = val; }
There is no overhead and data copying if the current thread is an object thread. Otherwise, the call will be deferred until the event loop in the object thread or in the main event loop if the object has no threads.
bool isSafe(QObject * obj) { Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread()); auto thread = obj->thread() ? obj->thread() : qApp->thread(); return thread == QThread::currentThread(); } template <typename Fun> void postCall(QObject * obj, Fun && fun) { qDebug() << __FUNCTION__; struct Event : public QEvent { using F = typename std::decay<Fun>::type; F fun; Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {} Event(const F & fun) : QEvent(QEvent::None), fun(fun) {} ~Event() { fun(); } }; QCoreApplication::postEvent( obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun))); }
Sending a call to a stream of objects.
This is the option above, but without using a functor. The postCall function can nullify parameters:
void method2(const QString &val) { if (!isSafe(this)) return postCall(this, &Class::method2, val); qDebug() << __FUNCTION__; str = val; }
Then:
template <typename Class, typename... Args> struct CallEvent : public QEvent { // See https://stackoverflow.com/a/7858971/1329652 // See also https://stackoverflow.com/a/15338881/1329652 template <int ...> struct seq {}; template <int N, int... S> struct gens { using type = typename gens<N-1, N-1, S...>::type; }; template <int ...S> struct gens<0, S...> { using type = seq<S...>; }; template <int ...S> void callFunc(seq<S...>) { (obj->*method)(std::get<S>(args)...); } Class * obj; void (Class::*method)(Args...); std::tuple<typename std::decay<Args>::type...> args; CallEvent(Class * obj, void (Class::*method)(Args...), Args&&... args) : QEvent(QEvent::None), obj(obj), method(method), args(std::move<Args>(args)...) {} ~CallEvent() { callFunc(typename gens<sizeof...(Args)>::type()); } }; template <typename Class, typename... Args> void postCall(Class * obj, void (Class::*method)(Args...), Args&& ...args) { qDebug() << __FUNCTION__; QCoreApplication::postEvent( obj->thread() ? static_cast<QObject*>(obj) : qApp, new CallEvent<Class, Args...>{obj, method, std::forward<Args>(args)...}); }
Object data protection
If a method works with a set of elements, access to these members can be serialized using a mutex. Use the QMutexLocker lever to express your intentions and avoid errors that were not detected in Mutex.
class MyClass : public QObject { Q_OBJECT QMutex m_mutex; int m_a; int m_b; public: void method(int a, int b) { QMutexLocker lock{&m_mutex}; m_a = a; m_b = b; }; };
The choice between using an object-specific mutex and invoking a method body in an object stream depends on the needs of the application. If all the members that are accessed in the method are private, then using the mutex makes sense, since we are under control and can, at our discretion, protect all access. Using an object-specific mutex also separates the method from competition in the object's event loop — so it can have performance advantages. On the other hand, the method must gain access to unsafe methods on objects that it does not have, then the mutex will not be enough, and the body of the method should be executed in the object stream.
Reading a simple element variable
If the const method reads a single piece of data that can be wrapped in a QAtomicInteger or QAtomicPointer , we can use an atomic field:
class MyClass : public QObject { QAtomicInteger<int> x; public:
Changing a simple element variable
If the method changes one piece of data that can be wrapped in QAtomicInteger or QAtomicPointer , and the operation can be performed using the atomic primitive, we can use the atomic field:
class MyClass : public QObject { QAtomicInteger<int> x; public:
This approach does not extend to the modification of several elements as a whole: intermediate states in which some members are changed and some others will not be visible to other threads. This usually violates the invariants that other code depends on.