Wiki

Qt 4's Multithreading Enhancements

by Jasmin Blanchette
Qt provides a portable API for creating and synchronizing threads since version 2.2, and offers the option of building the Qt library with or without thread support. With the emergence of multi-processor computers, multithreaded programming is rapidly gaining popularity. A recent customer survey tells us that over 50% of our licensees use threads in their applications.

Qt 3's support for multithreaded programming consists of the classes QThread, QThreadStorage, QMutex, QMutexLocker, QSemaphore, and QWaitCondition. In addition, many other Qt classes are reentrant, meaning that distinct instances of these classes can be used simultaneously in different threads.

Qt 4 extends Qt 3's multithreading support with the following improvements:

  • Threads created using QThread can now start their own event loops. As a consequence, QTimer and the networking classes are available in all threads.
  • Implicitly shared classes, such as QString and QMap, can now safely be copied across threads using operator=(). This effectively renders QDeepCopy superfluous.
  • Qt 4 is always built with thread support. In contrast, Qt 3 was available as two, binary incompatible libraries: a single-threaded library and a multithreaded library. Plugins created using one version couldn't be used in applications that linked against the other version.
  • Signals and slots can now work across threads, whereas previously slots were always invoked in the thread where the signal was emitted, even if the sender and receiver objects lived in different threads.
In this article, we will see how out we achieved this. Along the way, we will bump into some other benefits brought by Qt 4.0.

Atomic Reference-Counting

The first step in making Qt 4 more thread-friendly was to make Qt's implicitly shared classes really implicitly shared. With Qt 3, we must be careful when copying certain types of object (e.g., QString) across threads. From the Qt 3.3 documentation: Qt provides many implicitly shared and explicitly shared classes. In a multithreaded program, multiple instances of a shared class can reference shared data, which is dangerous if one or more threads attempt to modify the data. Qt provides the QDeepCopy class, which ensures that shared classes reference unique data. Internally, what happens when you take a copy is that two objects end up pointing to the same data, and the reference count associated to the data is incremented. This situation is illustrated by the diagram below:

Sharing

If the objects are then modified simultaneously from different threads, the reference count may get out of sync. This problem occurs because C++'s increment and decrement operators are not guaranteed to be atomic---indeed, they typically expand to three machine instructions:

  • Load the reference count variable in a register.
  • Increment or decrement the register's value.
  • Store the register's value back into the reference count variable.
The traditional solution to this problem would be to guard all accesses to the reference count with a QMutex. However, this would have disastrous consequences on Qt's performance. To avoid the performance penalty, Qt 4 uses a radically different approach: The reference counting is implemented using atomic operations written in assembly language.

This technology is available in our public API in the form of the QSharedData and QSharedDataPointer classes. These classes are perfect if you want to write your own implicitly shared classes and want them to be copiable across threads, just like Qt's built-in tool classes.

The Atomic Operations

The atomic operations are implemented in platform-specific header files located in Qt's "src/corelib/arch" directory. For example, the Intel 80386 implementation is located in "src/corelib/" "arch/i386/arch/qatomic.h". These operations are used internally by Qt and are not part of the public API. They may change in future versions of Qt. Qt 4 currently uses these six atomic operations:

  • q_atomic_increment(x) increments the integer *x by one. It returns a zero value if the new value is zero and a non-zero value if the new value is non-zero.
  • q_atomic_decrement(x) decrements the integer *x by one. It returns a zero value if the new value is zero and a non-zero value if the new value is non-zero.
  • q_atomic_set_int(x, n) assigns the integer n to *x and returns the previous value of *x.
  • q_atomic_set_ptr(x, p) assigns the pointer p to *x and returns the previous value of *x.
  • q_atomic_test_and_set_int(x, e, n) assigns the integer n to *x and returns a non-zero value, if *x equals e; otherwise it does nothing and returns zero.
  • q_atomic_test_and_set_ptr(x, e, p) assigns the pointer p to *x and returns a non-zero value, if *x equals e; otherwise it does nothing and returns zero.
On certain platforms, only the q_atomic_test_and_set_xxx() functions are implemented in assembly language, and the other four functions are implemented in C++ in terms of them. For example, here's how we can implement the atomic increment operation as a loop around q_atomic_test_and_set_int():

int q_atomic_increment(volatile int *x)
{
    register int oldValue;
    do {
        oldValue = *x;
    } while (!q_atomic_test_and_set_int(x, oldValue,
                                        oldValue + 1));
    return oldValue != -1;
}

In the loop, we atomically test that nobody altered *x behind our back and set the value to to oldValue + 1. If somebody did alter *x between the assignment to oldValue and the "test and set" operation, "test and set" fails and we try again.

Faster Mutexes and Semaphores

The Qt 3 implementation of QMutex uses the different platform's APIs for creating, locking, and unlocking mutexes. With this approach, the very common case where lock() is called when the mutex is unlocked is relatively expensive.

Atomic operations open the door to a more efficient implementation of mutexes and semaphores (since QSemaphore is built on top of QMutex). The trick involves an internal owner pointer, which points to the thread that currently owns the mutex. The pointer is null if the mutex is unlocked. An actual system mutex is used only when the lock is already held by another thread.

To implement this algorithm, the atomic "test and set" operation is used when accessing the owner pointer. Atomic reference counting is also used to wake up threads waiting for the mutex to unlock. The result is a QMutex class that is almost as fast as atomic reference counting.

Read-Write Lock

In Qt Quarterly 11, Volker Hilsheimer wrote an article about how to implement a read/write lock. The class was also released as a Qt Solution shortly after. Due to popular demand, we've now added QReadWriteLock to Qt, as well as two convenience locker classes (QReadLocker and QWriteLocker). From the Qt 4.0 documentation: A read-write lock is a synchronization tool for protecting resources that can be accessed for reading and writing. This type of lock is useful if you want to allow multiple threads to have simultaneous read-only access, but as soon as one thread wants to write to the resource, all other threads must be blocked until the writing is complete.

In many cases, QReadWriteLock is a direct competitor to QMutex. QReadWriteLock is a good choice if there are many concurrent reads and writing occurs infrequently. Like QMutex, the new QReadWriteLock uses atomic operations to optimize the common case where the lock isn't held for writing.

Per-Thread Event Loops

In most Qt applications, the event loop is started by calling QApplication::exec() from main(). Qt 4 also makes it possible to start thread-specific event loops by calling QThread::exec() from a QThread::run() reimplementation.

Event loops make it possible to use non-GUI classes such as QTimer, QTcpSocket, and QProcess, which require the presence of an event loop. They also make it possible to establish signal--slot connections across threads, as we will see in the next section.

When a QObject is created, it is associated with the thread that is currently running. The object is then said to "live" in that thread; all events sent to the object from any thread will be delivered by the object's thread. Objects can be moved to another thread after their creation using QObject::moveToThread().

Perthread

Signal--Slot Connections Across Threads

In Qt 3, the usual way to communicate with the GUI thread from a non-GUI thread was by posting a custom event to a QObject in the GUI thread. In Qt 4, this still works and can be generalized to the case where one thread needs to communicate with any other thread that has an event loop.

To ease programming, Qt 4 also allows you to establish signal--slot connections across threads. Behind the scenes, these connections are implemented using an event. If the signal has any parameters, these are also stored in the event. Like previously, if the sender and receiver live in the same thread, Qt makes a direct function call.

New Reentrant Classes

Some more Qt classes have been made reentrant in Qt 4:

Naturally, the new container classes (QList, QLinkedList, QVector, QHash, QSet, QCache, etc.) are also reentrant, just like their Qt 3 equivalents.

Documentation & Examples

Before you start using any of the features described in this article, we recommend that you first read the "Thread Support in Qt 4" page from the Qt reference documentation, available at "http://doc.trolltech.com/4.0/threads.php". It explains in details how to write multithreaded applications using Qt 4 and warns about common pitfalls.

Qt's "examples/threads" directory contains small programs that demonstrate Qt's thread classes. Particularly interesting is the Mandelbrot example, which shows how to use a worker thread to perform heavy computations without blocking the main thread's event loop, using signal--slot connections across threads.

Examples

If you're interested in network programming, you might also want to take a look at the Blocking Fortune Client and Threaded Fortune Server examples, which illustrate how to use QTcpSocket in a non-GUI thread (with a thread-specific event loop) and how to write multithreaded servers. These examples are located in Qt's "examples/network" directory.

Full documentation for a selection of the Qt 4.0 examples is available at "http://doc.trolltech.com/4.0/examples.php".


Copyright © 2005 Trolltech Trademarks