Qt 4's Multithreading Enhancements

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 14 | Документация

by Jasmin Blanchette

Qt provides a portable API for creating and synchronizing threadssince version 2.2, and offers the option of building the Qtlibrary with or without thread support. With the emergence ofmulti-processor computers, multithreaded programming is rapidlygaining popularity. A recent customer survey tells us that over50% 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 otherQt classes are reentrant, meaning that distinct instances ofthese classes can be used simultaneously in different threads.

Qt 4 extends Qt 3's multithreading support with the followingimprovements:

  • Threads created using QThread can now start their own eventloops. As a consequence, QTimer and the networking classes areavailable in all threads.
  • Implicitly shared classes, such as QString and QMap,can now safely be copied across threads using operator=(). Thiseffectively renders QDeepCopy superfluous.
  • Qt 4 is always built with thread support. In contrast, Qt 3 wasavailable as two, binary incompatible libraries: a single-threadedlibrary and a multithreaded library. Plugins created using oneversion couldn't be used in applications that linked against theother version.
  • Signals and slots can now work across threads, whereaspreviously slots were always invoked in the thread where the signalwas emitted, even if the sender and receiver objects lived indifferent 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'simplicitly shared classes really implicitly shared. With Qt 3, wemust 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. Ina multithreaded program, multiple instances of a shared class canreference shared data, which is dangerous if one or more threadsattempt 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 endup pointing to the same data, and the reference count associated tothe data is incremented. This situation is illustrated by the diagrambelow:

center


If the objects are then modified simultaneously from differentthreads, the reference count may get out of sync. This problem occursbecause C++'s increment and decrement operators are not guaranteedto be atomic---indeed, they typically expand to three machineinstructions:

  • Load the reference count variable in a register.
  • Increment or decrement the register's value.
  • Store the register's value back into the reference countvariable.

The traditional solution to this problem would be to guard allaccesses to the reference count with a QMutex. However, thiswould have disastrous consequences on Qt's performance. To avoid theperformance penalty, Qt 4 uses a radically different approach: Thereference counting is implemented using atomic operations written inassembly language.

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

[править] The Atomic Operations

The atomic operations are implemented in platform-specific headerfiles 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 usedinternally by Qt and are not part of the public API. They maychange in future versions of Qt. Qt 4 currently uses these six atomicoperations:

  • q_atomic_increment(x) increments the integer *x by one. Itreturns a zero value if the new value is zero and a non-zero value ifthe new value is non-zero.
  • q_atomic_decrement(x) decrements the integer *x by one. Itreturns a zero value if the new value is zero and a non-zero value ifthe 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) assignsthe integer n to *x and returns a non-zero value, if *xequals e; otherwise it does nothing and returns zero.
  • q_atomic_test_and_set_ptr(x, e, p) assignsthe pointer p to *x and returns a non-zero value, if *xequals 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 fourfunctions are implemented in C++ in terms of them. For example,here's how we can implement the atomic increment operation as a looparound 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 ourback and set the value to to oldValue + 1. If somebody did alter*x between the assignment to oldValue and the "test andset" operation, "test and set" fails and we try again.

[править] Faster Mutexes and Semaphores

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

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

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

[править] Read-Write Lock

In Qt Quarterly 11, Volker Hilsheimer wrote an article about howto implement a read/write lock. The class was also released as aQt 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.0documentation:A read-write lock is a synchronization tool for protectingresources that can be accessed for reading and writing. This typeof lock is useful if you want to allow multiple threads to havesimultaneous read-only access, but as soon as one thread wants towrite to the resource, all other threads must be blocked untilthe writing is complete.

In many cases, QReadWriteLock is a direct competitor to QMutex. QReadWriteLock is a good choice if there are manyconcurrent reads and writing occurs infrequently.Like QMutex, the new QReadWriteLock uses atomic operations tooptimize 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 possibleto 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 thepresence of an event loop. They also make it possible to establishsignal--slot connections across threads, as we will see in the next section.

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

center

[править] Signal--Slot Connections Across Threads

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

To ease programming, Qt 4 also allows you to establish signal--slotconnections across threads. Behind the scenes, these connections areimplemented using an event. If the signal has any parameters, theseare also stored in the event. Like previously, if the sender andreceiver 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" pagefrom the Qt reference documentation, available at"http://doc.trolltech.com/4.0/threads.php". It explains indetails how to write multithreaded applications using Qt 4 and warnsabout common pitfalls.

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

center

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

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