Grundlagen des Gewindeschneidens

Was sind Threads?

Bei Threads geht es darum, Dinge parallel zu tun, genau wie bei Prozessen. Wie unterscheiden sich Threads also von Prozessen? Während Sie Berechnungen in einer Tabellenkalkulation durchführen, läuft auf demselben Desktop vielleicht auch ein Media Player, der Ihren Lieblingssong abspielt. Hier sehen Sie ein Beispiel für zwei parallel laufende Prozesse: ein Tabellenkalkulationsprogramm und ein Media Player. Multitasking ist ein wohlbekannter Begriff für diesen Vorgang. Ein genauerer Blick auf den Media Player zeigt, dass innerhalb eines einzigen Prozesses wiederum mehrere Dinge parallel ablaufen. Während der Media Player Musik an den Audiotreiber sendet, wird die Benutzeroberfläche mit allem Drum und Dran ständig aktualisiert. Genau dafür sind Threads da - für die Gleichzeitigkeit innerhalb eines einzigen Prozesses.

Wie wird nun die Gleichzeitigkeit implementiert? Parallelarbeit auf Single-Core-CPUs ist eine Illusion, die der Illusion bewegter Bilder im Kino ähnelt. Bei Prozessen wird die Illusion dadurch erzeugt, dass die Arbeit des Prozessors an einem Prozess nach einer sehr kurzen Zeit unterbrochen wird. Dann geht der Prozessor zum nächsten Prozess über. Um zwischen den Prozessen zu wechseln, wird der aktuelle Programmzähler gespeichert und der Programmzähler des nächsten Prozessors geladen. Dies reicht jedoch nicht aus, da dasselbe mit Registern und bestimmten architektur- und betriebssystemspezifischen Daten geschehen muss.

So wie eine CPU zwei oder mehr Prozesse betreiben kann, ist es auch möglich, die CPU auf zwei verschiedenen Codesegmenten eines einzigen Prozesses laufen zu lassen. Wenn ein Prozess startet, führt er immer nur ein Codesegment aus, weshalb man sagt, dass der Prozess einen Thread hat. Das Programm kann jedoch beschließen, einen zweiten Thread zu starten. Dann werden innerhalb eines Prozesses zwei verschiedene Codesequenzen gleichzeitig verarbeitet. Gleichzeitigkeit wird auf Single-Core-CPUs dadurch erreicht, dass Programmzähler und Register wiederholt gespeichert und dann die Programmzähler und Register des nächsten Threads geladen werden. Es ist keine Mitwirkung des Programms erforderlich, um zwischen den aktiven Threads zu wechseln. Ein Thread kann sich in jedem beliebigen Zustand befinden, wenn der Wechsel zum nächsten Thread erfolgt.

Der aktuelle Trend bei der Entwicklung von CPUs geht zu mehreren Kernen. Eine typische Single-Thread-Anwendung kann nur einen Kern nutzen. Ein Programm mit mehreren Threads kann jedoch mehreren Kernen zugewiesen werden, so dass die Dinge wirklich gleichzeitig ablaufen. Die Verteilung der Arbeit auf mehr als einen Thread kann dazu führen, dass ein Programm auf Multicore-CPUs viel schneller läuft, da zusätzliche Kerne genutzt werden können.

GUI-Thread und Worker-Thread

Wie bereits erwähnt, hat jedes Programm einen Thread, wenn es gestartet wird. Dieser Thread wird als "Haupt-Thread" bezeichnet (in Qt-Anwendungen auch als "GUI-Thread" bekannt). Die Qt GUI muss in diesem Thread laufen. Alle Widgets und einige verwandte Klassen, z.B. QPixmap, funktionieren nicht in sekundären Threads. Ein sekundärer Thread wird üblicherweise als "Worker-Thread" bezeichnet, da er dazu dient, dem Haupt-Thread die Verarbeitungsarbeit abzunehmen.

Gleichzeitiger Zugriff auf Daten

Jeder Thread hat seinen eigenen Stack, d. h. jeder Thread hat seine eigene Aufrufhistorie und lokale Variablen. Im Gegensatz zu Prozessen teilen sich Threads denselben Adressraum. Das folgende Diagramm zeigt, wie die Bausteine von Threads im Speicher angeordnet sind. Der Programmzähler und die Register inaktiver Threads befinden sich normalerweise im Kernelbereich. Es gibt eine gemeinsame Kopie des Codes und einen separaten Stack für jeden Thread.

"Thread visualization"

Wenn zwei Threads einen Zeiger auf dasselbe Objekt haben, ist es möglich, dass beide Threads gleichzeitig auf dieses Objekt zugreifen, wodurch die Integrität des Objekts möglicherweise zerstört wird. Es ist leicht vorstellbar, was alles schief gehen kann, wenn zwei Methoden desselben Objekts gleichzeitig ausgeführt werden.

Manchmal ist es notwendig, von verschiedenen Threads aus auf ein Objekt zuzugreifen; zum Beispiel, wenn Objekte, die sich in verschiedenen Threads befinden, miteinander kommunizieren müssen. Da Threads denselben Adressraum verwenden, ist es für Threads einfacher und schneller, Daten auszutauschen, als dies für Prozesse der Fall ist. Die Daten müssen nicht serialisiert und kopiert werden. Die Übergabe von Zeigern ist möglich, aber es muss streng koordiniert werden, welcher Thread welches Objekt berührt. Die gleichzeitige Ausführung von Operationen auf einem Objekt muss verhindert werden. Es gibt mehrere Möglichkeiten, dies zu erreichen, und einige davon werden im Folgenden beschrieben.

Was kann also sicher gemacht werden? Alle in einem Thread erstellten Objekte können innerhalb dieses Threads sicher verwendet werden, vorausgesetzt, dass andere Threads keine Verweise auf sie haben und die Objekte keine implizite Kopplung mit anderen Threads aufweisen. Eine solche implizite Kopplung kann auftreten, wenn Daten zwischen Instanzen geteilt werden, wie bei statischen Mitgliedern, Singletons oder globalen Daten. Machen Sie sich mit dem Konzept der thread-sicheren und reentranten Klassen und Funktionen vertraut.

Verwendung von Threads

Es gibt grundsätzlich zwei Anwendungsfälle für Threads:

  • Beschleunigung der Verarbeitung durch die Nutzung von Multicore-Prozessoren.
  • Halten Sie den GUI-Thread oder andere zeitkritische Threads reaktionsfähig, indem Sie lang andauernde Verarbeitungen oder blockierende Aufrufe an andere Threads auslagern.

Wann sollten Alternativen zu Threads verwendet werden?

Entwickler müssen mit Threads sehr vorsichtig sein. Es ist leicht, andere Threads zu starten, aber sehr schwer, sicherzustellen, dass alle gemeinsam genutzten Daten konsistent bleiben. Probleme sind oft schwer zu finden, da sie nur ab und zu oder nur bei bestimmten Hardwarekonfigurationen auftreten können. Bevor Sie Threads zur Lösung bestimmter Probleme erstellen, sollten Sie mögliche Alternativen in Betracht ziehen.

AlternativeKommentar
QEventLoop::processEvents()Der wiederholte Aufruf von QEventLoop::processEvents() während einer zeitaufwändigen Berechnung verhindert das Blockieren der GUI. Diese Lösung ist jedoch nicht gut skalierbar, da der Aufruf von processEvents() je nach Hardware zu oft oder nicht oft genug erfolgen kann.
QTimerDie Hintergrundverarbeitung kann manchmal bequem mit einem Timer durchgeführt werden, um die Ausführung eines Slots zu einem bestimmten Zeitpunkt in der Zukunft zu planen. Ein Timer mit einem Intervall von 0 läuft ab, sobald es keine weiteren Ereignisse mehr zu verarbeiten gibt.
QSocketNotifier QNetworkAccessManager QIODevice::readyRead()Dies ist eine Alternative zu einem oder mehreren Threads, von denen jeder einen blockierenden Lesevorgang über eine langsame Netzwerkverbindung durchführt. Solange die Berechnung als Reaktion auf ein Paket von Netzwerkdaten schnell ausgeführt werden kann, ist dieses reaktive Design besser als das synchrone Warten in Threads. Reaktives Design ist weniger fehleranfällig und energieeffizient als Threading. In vielen Fällen ergeben sich auch Leistungsvorteile.

Im Allgemeinen wird empfohlen, nur sichere und getestete Pfade zu verwenden und die Einführung von Ad-hoc-Threading-Konzepten zu vermeiden. Das Modul QtConcurrent bietet eine einfache Schnittstelle für die Verteilung der Arbeit auf alle Kerne des Prozessors. Der Threading-Code ist vollständig im QtConcurrent Framework versteckt, so dass Sie sich nicht um die Details kümmern müssen. Allerdings kann QtConcurrent nicht verwendet werden, wenn eine Kommunikation mit dem laufenden Thread erforderlich ist, und es sollte nicht für blockierende Operationen verwendet werden.

Welche Qt Thread-Technologie sollten Sie verwenden?

Auf der Seite Multithreading-Technologien in Qt finden Sie eine Einführung in die verschiedenen Ansätze für Multithreading in Qt und Richtlinien für die Auswahl dieser Technologien.

Qt Thread-Grundlagen

Die folgenden Abschnitte beschreiben, wie QObjects mit Threads interagieren, wie Programme sicher auf Daten von mehreren Threads aus zugreifen können und wie asynchrone Ausführung zu Ergebnissen führt, ohne einen Thread zu blockieren.

QObject und Threads

Wie bereits erwähnt, müssen Entwickler immer vorsichtig sein, wenn sie Methoden von Objekten aus anderen Threads heraus aufrufen. Thread affinity ändert an dieser Situation nichts. Die Qt-Dokumentation kennzeichnet mehrere Methoden als thread-sicher. postEvent() ist ein bemerkenswertes Beispiel. Eine thread-sichere Methode kann von verschiedenen Threads aus gleichzeitig aufgerufen werden.

In Fällen, in denen es normalerweise keinen gleichzeitigen Zugriff auf Methoden gibt, kann der Aufruf von nicht-threadsicheren Methoden von Objekten in anderen Threads tausende Male funktionieren, bevor ein gleichzeitiger Zugriff erfolgt, was zu unerwartetem Verhalten führt. Das Schreiben von Testcode stellt die Thread-Korrektheit nicht vollständig sicher, ist aber dennoch wichtig. Unter Linux können Valgrind und Helgrind helfen, Threading-Fehler zu erkennen.

Schutz der Integrität von Daten

Wenn Sie eine Multithread-Anwendung schreiben, müssen Sie besonders darauf achten, dass die Daten nicht beschädigt werden. Siehe Synchronisierung von Threads für eine Diskussion über die sichere Verwendung von Threads.

Umgang mit asynchroner Ausführung

Eine Möglichkeit, das Ergebnis eines Worker-Threads zu erhalten, besteht darin, auf die Beendigung des Threads zu warten. In vielen Fällen ist ein blockierendes Warten jedoch nicht akzeptabel. Die Alternative zu einer blockierenden Wartezeit sind asynchrone Ergebnislieferungen mit entweder gebuchten Ereignissen oder Signalen und Slots in der Warteschlange. Dies erzeugt einen gewissen Overhead, da das Ergebnis einer Operation nicht in der nächsten Quelltextzeile erscheint, sondern in einem Slot, der sich irgendwo anders in der Quelltextdatei befindet. Qt-Entwickler sind es gewohnt, mit dieser Art von asynchronem Verhalten zu arbeiten, da es der ereignisgesteuerten Programmierung in GUI-Anwendungen sehr ähnlich ist.

Beispiele

Qt wird mit mehreren Beispielen für die Verwendung von Threads geliefert. Siehe die Klassenreferenzen für QThread und QThreadPool für einfache Beispiele. Auf der Seite Beispiele für Threading und gleichzeitige Programmierung finden Sie fortgeschrittenere Beispiele.

Tiefer gehen

Threading ist ein sehr kompliziertes Thema. Qt bietet mehr Klassen für Threading an, als wir in diesem Tutorium vorgestellt haben. Die folgenden Materialien können Ihnen helfen, das Thema zu vertiefen:

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.