Thursday, September 17, 2015
Qt Object Thread Affinity
For last whole year I have been totally lost between threads, mutexes, shared data, pointers, signals and slots and blah blah blah.
Actually I have learned a heck of lot about all these things in these few months. But I think I haven't grasped everything yet. There are many combinations that I don't know work or not. And I think there are many other tricks I am yet to learn especially when it comes to debugging.
Anyway, this post is about QThread and object affinity. Well, the typical QThread example is way too simple. They show an object containing one worker function and that worker function running in that thread when they start the thread. Beyond that, nothing! What happens if you call any function on that object, what happens? What about concurrency? What about when the thread is busy? Will the slots get queued up? Lots and lots of questions. But the real reason for all these questions is that I faced all these issues in last one year. And I had to make sense of things as usual while trying to wrap up the work.
But it was some invaluable learning from System design point of view. Lost of threads probably equals concurrency issues. Too few threads, application response slow because of slots getting queued up. So it becomes imperative to make sense of things.
Anyway, to make sense of things I have prepared a small example. This is based off Qt4.8. I know it's not the latest and there's a fair chance that in Qt 5.4 which is the latest as of writing this, there may be some changes. But I hope things don't make this totally obsolete.
So lets refer to the code above. What I have done is I have created a small GUI application, that has a three buttons. They trigger three signals in the MainWindow class. I have another class called PObject. I have used objects of this Class to demonstrate how signals/slots, object usage and direct function calls work when the object is moved to another thread (lets say worker thread).
So we have the first object pobj. I am right away moving this to worker thread. And then connecting the three signals to three slots on this object. I have connected first signal with Qt::DirectConnection. That's because I wanted to see the exact effect of direct connection has one the slot execution.
I have taken another object called nobj which I have shuffled a bit here and there. Basically at first I didn't move nobj to worker thread. So the result was as follows: The slot connected with Qt::DirectConnection always ran on the main thread, means it runs on the thread from which the signal is emitted. The other slots run on the worker thread. Also if you press buttons 2 and 3 one after another inside five seconds, then the slot for Button3 is queued on worker thread.
All good till this point, and just like things used to work. One important thing is, the standard example showed that the function which was connected to worker thread's started() signal ran on the worker thread. And they didn't show any other function or slot running on worker thread, which made me assume that only one function (or rather slot) ran on the worker thread. But this is not true, the thread stays running and the slots keep getting executed on that thread. This example proved that.
Now my next question was what about if we call any function from object pobj from mainWindow? Ideally it should run on main thread? Or does it runs on worker thread? Maybe because pobj is moved to worker thread?
Well, the calls to someFunction() on pobj and nobj prove that it's not the case. If you invoke any pobj/nobj function from main, then it will run on main thread. This is logical since when we think about it a bit, actually running it on any other thread doesn't make sense. I mean we don't know if that thread is busy or not, how much load it has, and also we have control over main thread so might as well execute this on main.
The real question here is what if this function is modifying some data? Then this function being a slot, there's probability that it may get invoked on the thread as a result of a signal emit. And this could cause a crash.
So yes, there's a possibility of concurrency issue. So we need to ensure that whatever data we are accessing is locked down with the help of mutex.
Now what would happen if we tried to move nobj to worker thread while worker thread is already running? As it happens you can do that. And then the slots will execute on worker thread depending on their connection type. Except maybe you need to ensure that the object is moved to the thread and only after the signals / slots are connected.
Huh... that was exhausting. But I think I have finally gotten a good grip of QThreads and the various issues surrounding them.