Posted In: Qt
There are basically two different ways of using QThread directly: Worker threads or subclassing. Those two are confusing to a lot of developers, and I will try to explain when you should use those. I will also explain how signals and slots work with threads and how they can help you or lead to problems.
There are other ways to use QThread than those two. There are hybrid cases that lie somewhere between the two general classes, and you can use QThreadPool or the QtConcurrent. Those cases will not be described here, but I might come back to those in later blog posts.
When you look at the discussion on whether or not to subclass QThread, you will find a lot of people who claim this is bad. For example the famous “You’re doing it wrong” blog post from 2010 (yes, this is an old discussion). I have mixed feelings about this blog post and the arguments it presents. On one hand, I actually encourage developers to follow the recommendations in this article, because I agree that this is the path that leads to fewer errors in your code. OTOH, there are certainly valid use cases for subclassing.
The fundamental problem here is that most developers do not really understand the thread model of Qt. And in some cases they do not even understand threading at all. There are a lot of developers in this industry who should not touch multithreaded code at all, but still have tasks in their daily job that force them to do this. I won’t try to educate general thread development here, but I will encourage everyone who needs to learn this to read a good book on multithreaded development. I’m only going to talk about the QThread class here and assume you understand threading.
So let’s get started on the issue at hand.
There are two Qt technologies that you need to understand before you can understand why QThread works the way it does.
QObject thread affinity
The first of those two technologies is thread affinity. Each QObject has a thread that it belongs to. Unless you set this manually, QObject chooses the current thread when the constructor is run.
Thread affinity has a bunch of subtle consequences for our objects, once you start going multithreaded. Of course, if you don’t have any threads, the objects all live in the main thread, and then you don’t have to worry about it. But with threading, you do have to worry about it.
The first thing you have to know is what this means for signals and slots.
When you do a connect from a signal to a slot, you have a choice of connection type. Those can be automatic, direct or queued. Yes, there are a couple of others, but those are not interesting from a threading point of view.
With a direct connection, the slot will be called by the current thread that is emitting the signal. Usually, this is very bad, if the two objects live in different threads. There are cases where you can do this – one example could be a slot that locks a mutex, adds something to a queue, unlocks and returns. But you have a problem if the object can be deleted by another thread while your slot is being executed, of course. My recommendation is not to use this unless you absolutely have to, you really know what you are doing, and make sure you document both sides very precisely.
For queued connections to work, the thread where the receiving object lives (i.e. has the thread affinity), must have an active event loop. This is not necessary for the sender. What happens is that the sending signal will create an event with the arguments of the signal and send this event to the receiver thread eventloop. In the eventloop, the event is transformed to a normal slot call. This happens no matter if the two objects live in the same thread or if they are in different threads. (And this is one of the simplest ways to send information from one thread to another.)
Automatic connection does a check in the signal whether the thread affinity of the receiver is the same as the current thread. If these are the same, it does a direct signal to slot call. If they are different, this is handled as a queued connection. This check is done every time the signal is emitted, which means the thread of the signal is irrelevant and the connect just works. Note that the signalling object does not have to live in a thread with an event loop.
QThread thread affinity
The second thing you need to understand, is the thread affinity of the QThread object. This is the part that confuses a lot of developers, although the rules are actually quite simple.
The thread affinity of a QThread object is the creating thread. It does not live in the thread that it models and implements. This may sound counterintuitive, until you actually think about this.
First, when you create the QThread object, the new thread doesn’t exist. This means the constructor will necessarily have to run in the thread that creates it. Also, when the thread is finished and you want to delete it, this will be done in the creating thread as well, so the destructor runs in the creating thread.
However, in the run method of QThread, you are now in the new thread.
This means when you create objects in a QThread subclass method, they will have different thread affinity based on the instantiation time. If you create an object in the constructor (or in the destructor, but that’s rare) this object will have the calling thread affinity. If you create an object in the run method, this will have the new thread affinity. If you have a method on the QThread subclass, an object created in here will (as usual) have the current thread affinity – so if you call it from the constructor it will have the creating thread affinity, and if you call it from run it will have the new thread affinity.
For signals, the thread affinity doesn’t matter. But for slots it matters a lot.
Now you can perhaps appreciate why subclassing QThread can be a problem. If you have anything using slots in the subclass objects, the thread affinity will mean that it will use the original thread. This is also the case for lambda slots or what other tricks you could come up with.
So, to “fix” this, developers try various workarounds. First, they use moveToThread(this) on the thread object. Unfortunately, this is probably the worst thing you can do, because that is a violation of the basic assumption of QThread itself. Others will connect using the DirectConnection flag, which actually works, but is a very brittle solution that probably will break while people are working on the code in the years to come.
The next problem you have to consider is when you instantiate other objects from the thread object itself. If you want an object to live in the new thread, you must instantiate it inside the run method and not set the thread object as the parent, because Qt does not like the parent-child relationship to go across from one thread to another.
So, now we’re finally ready for a list of what you should do in different cases.
If you have a task you need to run in a QThread, this is the place where you do as the QThread documentation says, and create a worker thread. Just instantiate a QThread object, instantiate the objects to live in this thread, call moveToThread(thread) on those objects and start the thread. This is what you should do for most cases.
If you have something that’s just a pure calculation or something that connects to hardware, then you will often use a QThread subclass. Yes, you can implement this using worker threads as well, but that’s usually a silly way to do it. (For the pure calculation case, I would actually use QtConcurrent or the QThreadPool, but that’s a topic for a later blog post.)
When you look at this recommendation, there are two things that are the defining questions:
- Do you need slots?
- Do you need QObject instances in the new thread?
If the answer to one of those questions is yes, then you should follow the standard recommendation and use a worker thread. If the answer is no, you have a choice between subclassing and worker threads.
If you answer yes to either of those questions and still choose subclassing, you are probably going to have issues that are really hard to find. Remember, there’s actually a reason people suggest that you don’t subclass QThread. If you do it, you expose yourself to a couple of sets of problems that you don’t have when you use a worker thread.