Synchronisierung von Threads
Obwohl der Zweck von Threads darin besteht, die parallele Ausführung von Code zu ermöglichen, gibt es Situationen, in denen Threads anhalten und auf andere Threads warten müssen. Wenn zum Beispiel zwei Threads gleichzeitig versuchen, in dieselbe Variable zu schreiben, ist das Ergebnis undefiniert. Das Prinzip, Threads zu zwingen, aufeinander zu warten, wird als gegenseitiger Ausschluss bezeichnet. Es ist eine gängige Technik, um gemeinsam genutzte Ressourcen wie Daten zu schützen.
Qt bietet sowohl Low-Level-Primitive als auch High-Level-Mechanismen zur Synchronisation von Threads.
Low-Level-Synchronisationsprimitive
QMutex ist die Basisklasse zur Durchsetzung des gegenseitigen Ausschlusses. Ein Thread sperrt einen Mutex, um Zugriff auf eine gemeinsame Ressource zu erhalten. Wenn ein zweiter Thread versucht, den Mutex zu sperren, während er bereits gesperrt ist, wird der zweite Thread in den Ruhezustand versetzt, bis der erste Thread seine Aufgabe beendet und den Mutex entsperrt hat.
QReadWriteLock ist ähnlich wie QMutex, mit der Ausnahme, dass es zwischen "lesendem" und "schreibendem" Zugriff unterscheidet. Wenn auf ein Stück Daten nicht geschrieben wird, ist es sicher, dass mehrere Threads es gleichzeitig lesen können. Ein QMutex zwingt mehrere Leser, sich beim Lesen gemeinsamer Daten abzuwechseln, aber ein QReadWriteLock erlaubt gleichzeitiges Lesen und verbessert so die Parallelität.
QSemaphore ist eine Verallgemeinerung von QMutex, die eine bestimmte Anzahl von identischen Ressourcen schützt. Im Gegensatz dazu schützt ein QMutex genau eine Ressource. Das Beispiel Producer and Consumer using Semaphores zeigt eine typische Anwendung von Semaphoren: die Synchronisierung des Zugriffs auf einen Ringpuffer zwischen einem Producer und einem Consumer.
QWaitCondition synchronisiert Threads nicht durch gegenseitigen Ausschluss, sondern durch die Bereitstellung einer Bedingungsvariablen. Während die anderen Primitive die Threads warten lassen, bis eine Ressource entsperrt ist, lässt QWaitCondition die Threads warten, bis eine bestimmte Bedingung erfüllt ist. Damit die wartenden Threads fortfahren können, rufen Sie wakeOne() auf, um einen zufällig ausgewählten Thread aufzuwecken, oder wakeAll(), um sie alle gleichzeitig aufzuwecken. Das Beispiel Producer and Consumer using Wait Conditions zeigt, wie man das Producer-Consumer-Problem mit QWaitCondition anstelle von QSemaphore lösen kann.
Hinweis: Die Synchronisationsklassen von Qt sind auf die Verwendung von korrekt ausgerichteten Zeigern angewiesen. Sie können zum Beispiel keine gepackten Klassen mit MSVC verwenden.
Diese Synchronisationsklassen können verwendet werden, um eine Methode thread-sicher zu machen. Dies ist jedoch mit Leistungseinbußen verbunden, weshalb die meisten Qt-Methoden nicht thread-sicher gemacht werden.
Risiken
Wenn ein Thread eine Ressource sperrt, aber nicht entsperrt, kann die Anwendung einfrieren, weil die Ressource für andere Threads dauerhaft nicht mehr verfügbar ist. Dies kann zum Beispiel passieren, wenn eine Ausnahme ausgelöst wird und die aktuelle Funktion zur Rückkehr zwingt, ohne ihre Sperre aufzuheben.
Ein ähnliches Szenario ist ein Deadlock. Nehmen wir zum Beispiel an, dass Thread A darauf wartet, dass Thread B eine Ressource entsperrt. Wenn Thread B ebenfalls darauf wartet, dass Thread A eine andere Ressource freigibt, werden beide Threads ewig warten, so dass die Anwendung einfriert.
Bequemlichkeitsklassen
QMutexLocker QReadLocker und sind Behelfsklassen, die die Verwendung von und erleichtern. Sie sperren eine Ressource, wenn sie erstellt werden, und geben sie automatisch wieder frei, wenn sie zerstört werden. Sie wurden entwickelt, um Code zu vereinfachen, der und verwendet, und so die Wahrscheinlichkeit zu verringern, dass eine Ressource versehentlich dauerhaft gesperrt wird. QWriteLocker QMutex QReadWriteLock QMutex QReadWriteLock
High-Level-Ereignis-Warteschlangen
Das Ereignissystem von Qt ist sehr nützlich für die Inter-Thread-Kommunikation. Jeder Thread kann seine eigene Ereignisschleife haben. Um einen Slot (oder eine beliebige invokable Methode) in einem anderen Thread aufzurufen, platzieren Sie diesen Aufruf in der Ereignisschleife des Zielthreads. Dadurch kann der Ziel-Thread seine aktuelle Aufgabe beenden, bevor der Slot läuft, während der ursprüngliche Thread parallel weiterläuft.
Um einen Aufruf in einer Ereignisschleife zu platzieren, stellen Sie eine Warteschlangen-Signal-Slot-Verbindung her. Immer wenn das Signal ausgesendet wird, werden seine Argumente vom Ereignissystem aufgezeichnet. Der Thread, den der Signalempfänger lives in aufruft, wird dann den Slot ausführen. Alternativ können Sie QMetaObject::invokeMethod() aufrufen, um den gleichen Effekt ohne Signale zu erzielen. In beiden Fällen muss ein queued connection verwendet werden, da ein direct connection das Ereignissystem umgeht und die Methode sofort im aktuellen Thread ausführt.
Bei der Verwendung des Ereignissystems für die Thread-Synchronisierung besteht im Gegensatz zur Verwendung von Low-Level-Primitiven kein Risiko von Deadlocks. Allerdings erzwingt das Ereignissystem keinen gegenseitigen Ausschluss. Wenn aufrufbare Methoden auf gemeinsame Daten zugreifen, müssen sie immer noch mit Low-Level-Primitiven geschützt werden.
Abgesehen davon bietet das Ereignissystem von Qt zusammen mit implizit gemeinsam genutzten Datenstrukturen eine Alternative zum traditionellen Thread Locking. Wenn ausschließlich Signale und Slots verwendet werden und keine Variablen zwischen Threads geteilt werden, kann ein Multithread-Programm ganz ohne Low-Level-Primitive auskommen.
Siehe auch QThread::exec() und Threads und QObjects.
© 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.