Threads und QObjects
QThread erbt QObject. Es sendet Signale aus, um anzuzeigen, dass der Thread gestartet oder beendet wurde, und bietet auch einige Slots.
Noch interessanter ist, dass QObjectin mehreren Threads verwendet werden kann, Signale aussendet, die Slots in anderen Threads aufrufen, und Ereignisse an Objekte sendet, die in anderen Threads "leben". Dies ist möglich, weil jeder Thread seine eigene Ereignisschleife haben darf.
QObject Reentrancy
QObject ist reentrancy. Die meisten seiner Nicht-GUI-Unterklassen, wie QTimer, QTcpSocket, QUdpSocket und QProcess, sind ebenfalls reentrant, so dass es möglich ist, diese Klassen von mehreren Threads aus gleichzeitig zu verwenden. Beachten Sie, dass diese Klassen so konzipiert sind, dass sie von einem einzigen Thread aus erstellt und verwendet werden können; es ist nicht garantiert, dass das Erstellen eines Objekts in einem Thread und der Aufruf seiner Funktionen von einem anderen Thread aus funktioniert. Es gibt drei Einschränkungen, die zu beachten sind:
- Das Kind einer QObject muss immer in dem Thread erstellt werden, in dem das Elternobjekt erstellt wurde. Dies bedeutet unter anderem, dass Sie niemals das Objekt QThread (
this
) als Elternteil eines im Thread erstellten Objekts übergeben sollten (da das Objekt QThread selbst in einem anderen Thread erstellt wurde). - Ereignisgesteuerte Objekte dürfen nur in einem einzigen Thread verwendet werden. Dies gilt insbesondere für den Timer-Mechanismus und den network module. Sie können beispielsweise keinen Timer starten oder einen Socket in einem Thread verbinden, der nicht der object's thread ist.
- Sie müssen sicherstellen, dass alle Objekte, die in einem Thread erstellt wurden, gelöscht werden, bevor Sie den QThread löschen. Dies lässt sich leicht bewerkstelligen, indem Sie die Objekte in Ihrer run()-Implementierung auf dem Stack erstellen.
Obwohl QObject reentrant ist, sind die GUI-Klassen, insbesondere QWidget und alle ihre Unterklassen, nicht reentrant. Sie können nur vom Hauptthread aus verwendet werden. Wie bereits erwähnt, muss QCoreApplication::exec() auch von diesem Thread aus aufgerufen werden.
In der Praxis kann die Unmöglichkeit, GUI-Klassen in anderen Threads als dem Hauptthread zu verwenden, leicht umgangen werden, indem zeitaufwändige Operationen in einen separaten Worker-Thread gelegt werden und die Ergebnisse im Hauptthread auf dem Bildschirm angezeigt werden, wenn der Worker-Thread fertig ist. Dies ist der Ansatz, der für die Implementierung des Mandelbrot-Beispiels und des Blocking Fortune Client-Beispiels verwendet wurde.
Im Allgemeinen wird das Erstellen von QObjects vor QApplication nicht unterstützt und kann je nach Plattform zu seltsamen Abstürzen beim Beenden führen. Das bedeutet, dass statische Instanzen von QObject ebenfalls nicht unterstützt werden. Eine gut strukturierte Single- oder Multi-Thread-Anwendung sollte dafür sorgen, dass die QApplication die erste erstellte und die letzte zerstörte QObject ist.
Pro-Thread-Ereignisschleife
Jeder Thread kann seine eigene Ereignisschleife haben. Der erste Thread startet seine Ereignisschleife mit QCoreApplication::exec(), oder für GUI-Anwendungen mit einem Dialog manchmal mit QDialog::exec(). Andere Threads können eine Ereignisschleife mit QThread::exec() starten. Wie QCoreApplication bietet QThread eine exit(int)-Funktion und einen quit()-Slot.
Eine Ereignisschleife in einem Thread ermöglicht es dem Thread, bestimmte Nicht-GUI Qt-Klassen zu verwenden, die das Vorhandensein einer Ereignisschleife erfordern (wie QTimer, QTcpSocket und QProcess). Sie ermöglicht es auch, Signale von beliebigen Threads mit Slots eines bestimmten Threads zu verbinden. Dies wird im Abschnitt " Signale und Slots über Threads hinweg " weiter unten näher erläutert.
Eine Instanz von QObject lebt in dem Thread, in dem sie erstellt wurde. Ereignisse für dieses Objekt werden von der Ereignisschleife dieses Threads weitergeleitet. Der Thread, in dem ein QObject lebt, ist über QObject::thread() verfügbar.
Die Funktion QObject::moveToThread() ändert die Thread-Affinität für ein Objekt und seine Kinder (das Objekt kann nicht verschoben werden, wenn es einen Elternteil hat).
Der Aufruf von delete
auf einem QObject von einem anderen Thread als dem, der das Objekt besitzt (oder der Zugriff auf das Objekt auf andere Weise) ist unsicher, es sei denn, Sie garantieren, dass das Objekt zu diesem Zeitpunkt keine Ereignisse verarbeitet. Verwenden Sie stattdessen QObject::deleteLater(), und es wird ein DeferredDelete -Ereignis gepostet, das die Ereignisschleife des Threads des Objekts schließlich aufgreift. Standardmäßig ist der Thread, dem ein QObject gehört, der Thread, der QObjecterstellt, jedoch nicht, nachdem QObject::moveToThread() aufgerufen wurde.
Wenn keine Ereignisschleife läuft, werden keine Ereignisse an das Objekt geliefert. Wenn Sie zum Beispiel ein QTimer Objekt in einem Thread erstellen, aber nie exec() aufrufen, wird QTimer nie sein timeout() Signal ausgeben. Der Aufruf von deleteLater() wird ebenfalls nicht funktionieren. (Diese Einschränkungen gelten auch für den Hauptthread.)
Sie können jederzeit manuell Ereignisse an ein beliebiges Objekt in einem beliebigen Thread senden, indem Sie die thread-sichere Funktion QCoreApplication::postEvent() verwenden. Die Ereignisse werden automatisch von der Ereignisschleife des Threads versendet, in dem das Objekt erstellt wurde.
Ereignisfilter werden in allen Threads unterstützt, mit der Einschränkung, dass sich das überwachende Objekt im gleichen Thread wie das überwachte Objekt befinden muss. In ähnlicher Weise kann QCoreApplication::sendEvent() (im Gegensatz zu postEvent()) nur verwendet werden, um Ereignisse an Objekte zu senden, die sich in dem Thread befinden, von dem aus die Funktion aufgerufen wird.
Zugriff auf QObject-Unterklassen von anderen Threads aus
QObject und alle seine Unterklassen sind nicht thread-sicher. Dies gilt auch für das gesamte System der Ereigniszustellung. Es ist wichtig, daran zu denken, dass die Ereignisschleife möglicherweise Ereignisse an Ihre QObject Unterklasse liefert, während Sie von einem anderen Thread aus auf das Objekt zugreifen.
Wenn Sie eine Funktion auf einer QObject Unterklasse aufrufen, die nicht im aktuellen Thread lebt, und das Objekt möglicherweise Ereignisse empfängt, müssen Sie alle Zugriffe auf die internen Daten Ihrer QObject Unterklasse mit einer Mutex schützen; andernfalls kann es zu Abstürzen oder anderem unerwünschten Verhalten kommen.
Wie andere Objekte auch, leben QThread Objekte in dem Thread, in dem das Objekt erstellt wurde - nicht in dem Thread, der beim Aufruf von QThread::run() erstellt wird. Es ist im Allgemeinen unsicher, Slots in Ihrer QThread Unterklasse bereitzustellen, es sei denn, Sie schützen die Mitgliedsvariablen mit einem Mutex.
Andererseits können Sie sicher Signale von Ihrer QThread::run()-Implementierung ausgeben, da die Signalausgabe thread-sicher ist.
Signale und Slots über Threads hinweg
Qt unterstützt diese Signal-Slot-Verbindungstypen:
- Auto Connection (Standard) Wenn das Signal in dem Thread ausgesendet wird, zu dem das empfangende Objekt eine Affinität hat, dann ist das Verhalten dasselbe wie bei der direkten Verbindung. Andernfalls ist das Verhalten dasselbe wie bei der Warteschlangenverbindung."
- Direct Connection Der Slot wird sofort aufgerufen, wenn das Signal ausgesendet wird. Der Slot wird im Thread des Senders ausgeführt, der nicht unbedingt der Thread des Empfängers ist.
- Queued Connection Der Slot wird aufgerufen, wenn die Kontrolle zur Ereignisschleife des Threads des Empfängers zurückkehrt. Der Slot wird im Thread des Empfängers ausgeführt.
- Blocking Queued Connection Der Slot wird wie bei der Queued Connection aufgerufen, außer dass der aktuelle Thread blockiert, bis der Slot zurückkehrt.
Hinweis: Die Verwendung dieses Typs zur Verbindung von Objekten im selben Thread führt zu einem Deadlock.
- Unique Connection Das Verhalten ist dasselbe wie bei der Auto-Verbindung, aber die Verbindung wird nur hergestellt, wenn sie keine bestehende Verbindung dupliziert, d. h. wenn das gleiche Signal bereits mit dem gleichen Slot für das gleiche Objektpaar verbunden ist, wird die Verbindung nicht hergestellt und connect() gibt
false
zurück.
Der Verbindungstyp kann durch Übergabe eines zusätzlichen Arguments an connect() angegeben werden. Beachten Sie, dass die Verwendung direkter Verbindungen, wenn Sender und Empfänger in verschiedenen Threads leben, unsicher ist, wenn eine Ereignisschleife im Thread des Empfängers läuft, aus dem gleichen Grund, aus dem der Aufruf einer Funktion auf einem Objekt, das in einem anderen Thread lebt, unsicher ist.
QObject::connect() selbst ist thread-sicher.
Das Mandelbrot-Beispiel verwendet eine Warteschlangenverbindung für die Kommunikation zwischen einem Arbeits-Thread und dem Haupt-Thread. Um das Einfrieren der Ereignisschleife des Hauptthreads (und damit der Benutzeroberfläche der Anwendung) zu vermeiden, wird die gesamte Mandelbrot-Fraktalberechnung in einem separaten Worker-Thread durchgeführt. Der Thread gibt ein Signal aus, wenn er mit dem Rendering des Fraktals fertig ist.
In ähnlicher Weise verwendet das Beispiel des Blocking Fortune Client einen separaten Thread für die asynchrone Kommunikation mit einem TCP-Server.
© 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.