Oft ist es notwendig, dass mehrere Operation nebeneinander (parallel) ausgeführt werden. Dies Funktionalität wird annähernd mittels Threads erreicht. Das Betriebssystem wechselt nach einer gewissen Zeit (je nach Implementierung der Prozessverwaltung des Betriebssystems) zwischen dem Hauptthread und den dazugehörenden Threads. Dadurch entsteht eine sogenannte Pseudo-Parallelität
.
Threads teilen sich den Speicherbereich untereinander und mit dem Hauptthread. Daher kann es zu unerwarteten Ergebnissen kommen, wenn von mehreren Stellen des Programms auf die gleichen Variablen zugegriffen wird. Abhilfe schaffen Mutexes
und Semaphore
.
Programmteile werden in Threads ausgelagert, weil sie lange dauern bzw. sie auf ein bestimmtes Ereignis warten. Warten nun mehrere Threads aufeinander, steht das Programm, man ist einem sogenannten Deadlock
gefangen.
Die Klasse QThread
bietet Funktionen zur Verwaltung eines Threads. Um einen Thread auszuführen, muss zuerst eine Instanz von QApplication
bzw. QCoreApplication
erstellt werden. Der Thread in dem dies geschieht wird zum Hauptthread. Die eigentliche Implementierung wird ein einem anderen von QObject
abgeleiteten sogenannten Worker-Objekt
vorgenommen. Innerhalb des Worker-Objekts existiert ein Slot, der die eigentliche Arbeit verrichtet. Das Worker-Objekt muss zuerst das Event-Handling von dem Hauptthread in den anderen Thread verlegen, dies geschieht über die Methode QObject::moveToThread()
. Bevor der Thread noch gestartet wird, muss der Slot im Worker-Objekt noch mit einem Signal des QThread
-Objekts verbunden werden. Im einfachsten Fall ist das started()
, man kann aber auch QThread
ableiten und ein benutzerdefiniertes Signal auslösen. Bei dieser Methode können z.B. noch eventuell nötige Initialiserungen vorgenommen werden, bevor das Worker-Objetk seine Arbeit beginnen kann. Weiters muss im Slot der Worker-Klasse thread()→quit()
aufgerufen werden.
Folgendes Beispiel gibt im Hauptthread und einem weiteren Thread die Zahlen von 0 bis 19 parallel aus.
// main.cpp #include "ThreadWorker.h" #include <QCoreApplication> #include <QThread> #include <QDebug> int main( int argc, char *argv[] ) { // Es muss ein QCoreApplication-Objekt erstellt werden! QCoreApplication app( argc, argv ); // Objekt zur Verwaltung des Threads. QThread *thread = new QThread(); // Worker-Objekt wird erstellt. ThreadWorker *worker = new ThreadWorker(); // Mit der Arbeit wird sofort begonnen, nachdem der Thread gestartet wurde. QObject::connect( thread, SIGNAL( started() ), worker, SLOT( work() ) ); // Das Programm wird beendet, wenn der Thread fertig ist. QObject::connect( thread, SIGNAL( finished() ), &app, SLOT( quit() ) ); // Event-Handling des Worker-Objekts wird in den Thread verlagert. worker->moveToThread( thread ); // Thread wird gestartet. thread->start(); for( int i = 0; i < 20; i++ ) qDebug() << "main:" << i; // Event-Schleife starten und falls notwendig auf den Thread warten. return app.exec(); }
// ThreadWorker.h #ifndef THREADWORKER_H #define THREADWORKER_H #include <QObject> class ThreadWorker : public QObject { Q_OBJECT public slots: void work(); }; #endif // THREADWORKER_H
// ThreadWorker.cpp #include "ThreadWorker.h" #include <QDebug> #include <QThread> // Hier wird die eigentliche Arbeit durchgeführt. void ThreadWorker::work() { for( int i = 0; i < 20; i++ ) qDebug() << "thread:" << i; // Thread beenden. thread()->quit(); }
Beispielausgabe:
main: 0 main: 1 main: 2 main: 3 main: 4 main: 5 main: 6 main: 7 main: 8 main: 9 main: 10 main: 11 main: 12 main: 13 main: 14 thread: 0 main: 15 thread: 1 thread: 2 main: 16 main: 17 thread: 3 thread: 4 thread: 5 thread: 6 main: 18 main: 19 thread: 7 thread: 8 thread: 9 thread: 10 thread: 11 thread: 12 thread: 13 thread: 14 thread: 15 thread: 16 thread: 17 thread: 18 thread: 19
Die Parallelität ist an der Durchmischung von main
und thread
zu erkennen. An der Ausgabe kann man sehen, dass es eine Weile dauert, bis der Thread überhaupt mit seiner Arbeit beginnt.
Der Hauptthread darf nie blockiert werden. Das verhindert das Zeichnen von Widgets, unterbricht die Netzwerkkommunikation und Timer funktionieren nicht mehr.
An vielen Stellen (selbst in der offiziellen Qt-Dokumentation) wird run()
noch direkt QThread
implementiert. Dies ist jedoch nicht mehr der empfohlene Weg. QThread
soll dazu dienen einen Thread zu verwalten, es ist aber nicht das Objekt, das den Code enthält welcher parallel ausgeführt werden soll (Quelle).
Der QtConcurrency
-Namepsace bietet eine High-Level-API für die Verwendung von Threads, bei der man sich nicht über Dinge wie Mutexes oder Semaphore sorgen muss.
Die Rechenleistung von Grafikkarten wird immer öfter für Berechnungen verwendet. Grafikkarten besitzen oft hunderte Prozessoren, die parallele Arbeiten erledigen können. Die Hersteller bieten dafür verschiedene Bibliotheken an, wie z.B. Nvidias CUDA. Da diese Bibliotheken jedoch nicht nur system-, sondern auch hardwareabhängig sind, ist es sehr schwierig Programme möglichst plattformunabhängig zu schreiben. Abhilfe schafft die ursprünglich von Apple entwickelte Bibliothek OpenCL. Um Qt-Entwicklern die Arbeit zu erleichtern, gibt es mit QtOpenCL im Qt-Stil entwickelte Wrapper-Klassen um OpenCL zu verwenden.