====== Threads ====== ===== Was ist ein Thread? ==== 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''. ===== Welche Probleme können auftreten? ===== ==== Geteilter Speicher ==== 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''. ==== Deadlocks ==== 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. ===== Plattformunabhängige Implementierung mit Qt ===== ==== QThread ==== === Vorbereitung und Implementierung === 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. \\ === Beispiel === Folgendes Beispiel gibt im Hauptthread und einem weiteren Thread die Zahlen von 0 bis 19 parallel aus. // main.cpp #include "ThreadWorker.h" #include #include #include 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 class ThreadWorker : public QObject { Q_OBJECT public slots: void work(); }; #endif // THREADWORKER_H // ThreadWorker.cpp #include "ThreadWorker.h" #include #include // 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. === Qt-spezifische Probleme === == Blockieren des Hauptthreads == Der Hauptthread darf nie blockiert werden. Das verhindert das Zeichnen von Widgets, unterbricht die Netzwerkkommunikation und Timer funktionieren nicht mehr. == Implementierung des Threads == 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 ([[http://developer.qt.nokia.com/doc/qt-4.8/qthread.html#note-5|Quelle]]). ==== QtConcurrency ==== 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. \\ FIXME ===== Der nächste Schritt: GPGPU (QtOpenCL) ===== 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. [[http://www.nvidia.com/object/cuda_home_new.html|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 [[http://www.khronos.org/opencl/|OpenCL]]. Um Qt-Entwicklern die Arbeit zu erleichtern, gibt es mit [[http://doc.qt.nokia.com/opencl-snapshot/|QtOpenCL]] im Qt-Stil entwickelte Wrapper-Klassen um OpenCL zu verwenden.