Suppose we wrote a non-const method in a QObject
-deriving class:
class MyClass : public QObject {
int x;
public:
void method(int a) {
x = a; // and possibly other things
};
};
We want to make that method thread-safe: meaning that calling it from an arbitrary thread, and from multiple threads concurrently, shouldn't introduce undefined behavior.
What mechanisms/APIs does Qt provide to help us make that method thread-safe?
What mechanisms/APIs from Qt one could use when the method does the "other things" too?
Is there any classification possible of the "other things" that would inform what Qt-specific mechanisms/APIs to use?
Off topic are mechanisms provided by the C++ standard itself, and generic/non-Qt-specific ways of ensuring thread-safety.
QObject and all of its subclasses are not thread-safe. This includes the entire event delivery system. It is important to keep in mind that the event loop may be delivering events to your QObject subclass while you are accessing the object from another thread.
There is no rule that makes the code thread safe, the only thing you can do is make sure that your code will work no matter how many times is it being actively executed, each thread can be interrupted at any point, with each thread being in its own state/location, and this for each function (static or otherwise) that ...
It is not thread safe, you cannot enqueue and dequeue concurrently from different thread.
Thread-safe Initialization of Data If shared data is read-only, it's sufficient to initialize it in a thread-safe way. C++ offers various ways to achieve this including using constant expression, a static variable with block scope, or using the function std::call_once in combination with the flag std::once_flag .
The applicable Qt APIs depend on what is the functionality of the thread-safe method. Let's cover the circumstances from the most general to most specific.
The bodies of signals are generated by the moc
tool and are thread-safe.
Corollary 1: All directly-connected slots/functors must be thread-safe: doing otherwise breaks the contract of a signal. While the signal-slot system allows decoupling of code, the specific case of a direct connection leaks the requirements of a signal to the connected code!
Corollary 2: Direct connections couple tighter than automatic connections.
The most general approach is that of ensuring that the method's is always executed in the object's thread()
. This makes it thread-safe in respect to the object, but of course the use of any other objects from within the method must be done thread-safely too.
In general, a thread-unsafe method can only be called from the object's thread()
:
void MyObject::method() {
Q_ASSERT(thread() == QThread::currentThread());
...
}
The special case of a thread-less object requires some care. An object becomes thread-less when its thread finishes. Yet, just because the object is thread-less doesn't make all of its methods thread-safe. It would be preferable to choose one thread to "own" such objects for the purpose of thread-safety. Such thread might be the main thread:
Q_ASSERT(QThread::currentThread() == (thread() ? thread() : qApp()->thread()));
Our job is to fulfill that assertion. Here's how:
Leverage thread-safe signals.
Since signals are thread-safe, we could make our method a signal, and host its implementation in a 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 uphold the assertion, but is verbose and performs an additional dynamic allocation per each argument (as of Qt 5.7 at least).
Dispatch the call in a functor to the object's thread.
There are many ways of doing it; let's present one that does the minimum number of dynamic allocations: in most cases, exactly one.
We can wrap the call of the method in a functor and ensure that it's executed thread-safely:
void method1(int val) {
if (!isSafe(this))
return postCall(this, [=]{ method1(val); });
qDebug() << __FUNCTION__;
num = val;
}
There is no overhead and no copying of data if the current thread is the object's thread. Otherwise, the call will be deferred to the event loop in the object's thread, or to the main event loop if the object is threadless.
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)));
}
Dispatch the call to the object's thread.
This is a variation on the above, but without using a functor. The postCall
function can wrap the parameters explicitly:
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)...});
}
If the method operates on a set of members, the access to these members can be serialized by using a mutex. Leverage QMutexLocker
to express your intent and avoid unreleased mutex errors by construction.
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 the body of the method in the object's thread depends on the needs of the application. If all of the members accessed in the method are private then using a mutex makes sense since we're in control and can ensure, by design, that all access is protected. The use of object-specific mutex also decouples the method from the contention on the object's event loop - so might have performance benefits. On the other hand, is the method must access thread-unsafe methods on objects it doesn't own, then a mutex would be insufficient, and the method's body should be executed in the object's thread.
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:
/// Thread-Safe
int method() const {
return x.load();
};
};
If the method modifies a single piece of data that can be wrapped in QAtomicInteger
or QAtomicPointer
, and the operation can be done using an atomic primitive, we can use an atomic field:
class MyClass : public QObject {
QAtomicInteger<int> x;
public:
/// Thread-Safe
void method(int a) {
x.fetchAndStoreOrdered(a);
};
};
This approach doesn't extend to modifying multiple members in general: the intermediate states where some members are changed and some other are not will be visible to other threads. Usually this would break invariants that other code depends on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With