class Thread : public QThread
Q_OBJECT
protected:
void run()
Object* myObject = new Object();
connect(myObject, &Object::started,
this, &Thread::doWork);
exec();
private slots:
void doWork();
The started()signal will be processed by the Thread event loop only upon the exec() call. It will block and wait until QThread::exit() is called.
A crucial thing to note is that a thread event loop delivers events for all QObject classes that are living in that thread. This includes all objects created in that thread or moved to that thread. This is referred to as the thread affinity of an object. Here's an example:
class Thread : public QThread
Thread() :
mObject(new QObject())
private :
QObject* myObject;
// Somewhere in MainWindow
Thread thread;
thread.start();
In this snippet, myObject is constructed in the Thread constructor, which is created in turn in MainWindow. At this point, thread is living in the GUI thread. Hence, myObject is also living in the GUI thread.
An object created before a QCoreApplication object has no thread affinity. As a consequence, no event will be dispatched to it.
It is great to be able to handle signals and slots in our own QThread, but how can we control signals across multiple threads? A classic example is a long running process that is executed in a separate thread that has to notify the UI to update some state:
class Thread : public QThread
Q_OBJECT
void run() {
// long running operation
emit result("I <3 threads");
signals:
void result(QString data);
// Somewhere in MainWindow
Thread* thread = new Thread(this);
connect(thread, &Thread::result, this, &MainWindow::handleResult);
connect(thread, &Thread::finished, thread, &QObject::deleteLater);
thread->start();
Intuitively, we assume that the first connect function sends the signal across multiple threads (to have a result available in MainWindow::handleResult), whereas the second connect function should work on thread's event loop only.
Fortunately, this is the case due to a default argument in the connect() function signature: the connection type. Let's see the complete signature:
QObject::connect(
const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type = Qt::AutoConnection)
The type variable takes Qt::AutoConnection as a default value. Let's review the possible values of Qt::ConectionType enum as the official Qt documentation states:
Qt::AutoConnection: If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.
Qt::DirectConnection: This slot is invoked immediately when the signal is emitted. The slot is executed in the signaling thread.
Qt::QueuedConnection: The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
Qt::BlockingQueuedConnection: This is the same as Qt::QueuedConnection, except that the signaling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signaling thread or else the application will deadlock.
Qt::UniqueConnection: This is a flag that can be combined with any one of the preceding connection types, using a bitwise OR element. When Qt::UniqueConnection is set, QObject::connect() will fail if the connection already exists (that is, if the same signal is already connected to the same slot for the same pair of objects).
When using Qt::AutoConnection, the final ConnectionType is resolved only when the signal is effectively emitted. If you look again at our example, the first connect():
connect(thread, &Thread::result,
this, &MainWindow::handleResult);
When the result() signal will be emitted, Qt will look at the handleResult() thread affinity, which is different from the thread affinity of the result() signal. The thread object is living in MainWindow (remember that it has been created in MainWindow), but the result() signal has been emitted in the run() function, which is running in a different thread of execution. As a result, a Qt::QueuedConnection function will be used.
We will now take a look at the second connect():
connect(thread, &Thread::finished, thread, &QObject::deleteLater);
Here, deleteLater() and finished() live in the same thread, therefore, a Qt::DirectConnection will be used.
It is crucial that you understand that Qt does not care about the emitting object thread affinity, it looks only at the signal's "context of execution."
Loaded with this knowledge, we can take another look at our first QThread example to have a complete understanding of this system:
class Thread : public QThread
Q_OBJECT
protected:
void run()
Object* myObject = new Object();
connect(myObject, &Object::started,
this, &Thread::doWork);
exec();
private slots:
void doWork();
When Object::started() is emitted, a Qt::QueuedConnection function will be used. his is where your brain freezes. The Thread::doWork() function lives in another thread than Object::started(), which has been created in run(). If the Thread has been instantiated in the UI Thread, this is where doWork() would have belonged.
This system is powerful but complex. To make things more simple, Qt favors the worker model. It splits the threading plumbing from the real processing. Here is an example:
class Worker : public QObject
Q_OBJECT
public slots:
void doWork()
emit result("workers are the best");
signals:
void result(QString data);
// Somewhere in MainWindow
QThread* thread = new Thread(this);
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(thread, &QThread::finished,
worker, &QObject::deleteLater);
connect(this, &MainWindow::startWork,
worker, &Worker::doWork);
connect(worker, &Worker::resultReady,
this, handleResult);
thread->start();
// later on, to stop the thread
thread->quit();
thread->wait();
We start by creating a Worker class that has the following:
A doWork()slot that will have the content of our old QThread::run() function
A result()signal that will emit the resulting data
Next, in MainWindow, we create a simple thread and an instance of Worker. The worker->moveToThread(thread) function is where the magic happens. It changes the affinity of the worker object. The worker now lives in the thread object.
You can only push an object from your current thread to another thread. Conversely, you cannot pull an object that lives in another thread. You cannot change the thread affinity of an object if the object does not live in your thread. Once thread->start() is executed, we cannot call worker->moveToThread(this) unless we are doing it from this new thread.
After that, we will use three connect() functions:
We handle worker life cycle by reaping it when the thread is finished. This signal will use a Qt::DirectConnection function.
We start the Worker::doWork() upon a possible UI event. This signal will use a Qt::QueuedConnection.
We process the resulting data in the UI thread with handleResult(). This signal will use a Qt::QueuedConnection.
To sum up, QThread can be either subclassed or used in conjunction with a worker class. Generally, the worker approach is favored because it separates more cleanly the threading affinity plumbing from the actual operation you want to execute in parallel.
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
Flying over Qt multithreading technologies
Built upon QThread, several threading technologies are available in Qt. First, to synchronize threads, the usual approach is to use a mutual exclusion (mutex) for a given resource. Qt provides it by the mean of the QMutex class. Its usage is straightforward:
QMutex mutex;
int number = 1;
mutex.lock();
number *= 2;
mutex.unlock();
From the mutex.lock() instruction, any other thread trying to lock the mutex object will wait until mutex.unlock() has been called.
The locking/unlocking mechanism is error prone in complex code. You can easily forget to unlock a mutex in a specific exit condition, causing a deadlock. To simplify this situation, Qt provides a QMutexLocker that should be used where the QMutex needs to be locked:
QMutex mutex;
QMutexLocker locker(&mutex);
int number = 1;
number *= 2;
if (overlyComplicatedCondition) {
return;
} else if (notSoSimple) {
return;
The mutex is locked when the locker object is created, and it will be unlocked when locker is destroyed, for example, when it goes out of scope. This is the case for every condition we stated where the return statement appears. It makes the code simpler and more readable.
If you need to create and destroy threads frequently, managing QThread instances by hand can become cumbersome. For this, you can use the QThreadPool class, which manages a pool of reusable QThreads.
To execute code within threads managed by a QThreadPool, you will use a pattern very close to the worker we covered earlier. The main difference is that the processing class has to extend the QRunnable class. Here is how it looks:
class Job : public QRunnable
void run()
// long running operation
Job* job = new Job();
QThreadPool::globalInstance()->start(job);
Just override the run() function and ask QThreadPool to execute your job in a separate thread. The QThreadPool::globalInstance() function is a static helper function that gives you access to an application global instance. You can create your own QThreadPool class if you need to have a finer control over the QThreadPool life cycle.
Note that QThreadPool::start() takes the ownership of the job object and will automatically delete it when run() finishes. Watch out, this does not change the thread affinity like QObject::moveToThread() does with workers! A QRunnable class cannot be reused, it has to be a freshly baked instance.
If you fire up several jobs, QThreadPool automatically allocates the ideal number of threads based on the core count of your CPU. The maximum number of threads that the QThreadPool class can start can be retrieved with QThreadPool::maxThreadCount().
If you need to manage threads by hand, but you want to base it on the number of cores of your CPU, you can use the handy static function, QThreadPool::idealThreadCount().
Another approach to multithreaded development is available with the Qt Concurrent framework. It is a higher level API that avoids the use of mutexes/locks/wait conditions and promotes the distribution of the processing among CPU cores.
Qt Concurrent relies of the QFuture class to execute a function and expect a result later on:
void longRunningFunction();
QFuture<void> future = QtConcurrent::run(longRunningFunction);
The longRunningFunction() will be executed in a separated thread obtained from the default QThreadPool class.
To pass parameters to a QFuture class and retrieve the result of the operation, use the following code:
QImage processGrayscale(QImage& image);
QImage lenna;
QFuture<QImage> future = QtConcurrent::run(processGrayscale,
lenna);
QImage grayscaleLenna = future.result();
Here, we pass lenna as a parameter to the processGrayscale() function. Because we want a QImage as a result, we declare QFuture with the template type QImage. After that, future.result() blocks the current thread and waits for the operation to be completed to return the final QImage template type.
To avoid blocking, QFutureWatcher comes to the rescue:
QFutureWatcher<QImage> watcher;
connect(&watcher, &QFutureWatcher::finished,
this, &QObject::handleGrayscale);
QImage processGrayscale(QImage& image);
QImage lenna;
QFuture<QImage> future = QtConcurrent::run(processImage, lenna);
watcher.setFuture(future);
We start by declaring a QFutureWatcher with the template argument matching the one used for QFuture. Then, simply connect the QFutureWatcher::finished signal to the slot you want to be called when the operation has been completed.
The last step is to the tell the watcher to watch the future object with watcher.setFuture(future). This statement looks almost like it's coming from a science fiction movie.
Qt Concurrent also provides a MapReduce and FilterReduce implementation. MapReduce is a programming model that basically does two things:
Map or distribute the processing of datasets among multiple cores of the CPU
Reduce or aggregate the results to provide it to the caller
check styleThis technique has been first promoted by Google to be able to process huge datasets within a cluster of CPU.
Here is an example of a simple Map operation:
QList images = ...;
QImage processGrayscale(QImage& image);
QFuture<void> future = QtConcurrent::mapped(
images, processGrayscale);
Instead of QtConcurrent::run(), we use the mapped function that takes a list and the function to apply to each element in a different thread each time. The images list is modified in place, so there is no need to declare QFuture with a template type.
The operation can be made a blocking operation using QtConcurrent::blockingMapped() instead of QtConcurrent::mapped().
Finally, a MapReduce operation looks like this:
QList images = ...;
QImage processGrayscale(QImage& image);
void combineImage(QImage& finalImage, const QImage& inputImage);
QFuture<void> future = QtConcurrent::mappedReduced(
images,
processGrayscale,
combineImage);
Here, we added a combineImage() that will be called for each result returned by the map function, processGrayscale(). It will merge the intermediate data, inputImage, into the finalImage. This function is called only once at a time per thread, so there is no need to use a mutex object to lock the result variable.
The FilterReduce reduce follows exactly the same pattern, the filter function simply allows to filter the input list instead of transforming it.
Summary
In this article, we discovered how a QThread works and you learned how to efficiently use tools provided by Qt to create a powerful multi-threaded application.
Resources for Article:
Further resources on this subject:
QT Style Sheets [article]
GUI Components in Qt 5 [article]
DOM and QTP [article]
Do not want to be too picky, but in QFutureWatcher example shouldn't the line
QFuture future = QtConcurrent::run(processImage, lenna);
QFuture future = QtConcurrent::run(processGrayscale, lenna);
Company Address: Packt Publishing Ltd, Grosvenor House, 11 St Paul's Square, Birmingham, B3 1RB
© 2024 Packt Publishing Limited All Rights Reserved