Auf dieser Seite

Synchronisierung von Threads

Zwar dienen Threads dazu, Code parallel ausführen zu lassen, doch gibt es Situationen, in denen Threads anhalten und auf andere Threads warten müssen. Wenn beispielsweise zwei Threads gleichzeitig versuchen, in dieselbe Variable zu schreiben, ist das Ergebnis undefiniert. Das Prinzip, Threads dazu zu zwingen, aufeinander zu warten, wird als gegenseitiger Ausschluss bezeichnet. Es handelt sich um eine gängige Technik zum Schutz gemeinsam genutzter Ressourcen wie beispielsweise Daten.

Qt bietet sowohl Low-Level-Primitiven als auch High-Level-Mechanismen zur Synchronisierung von Threads.

Low-Level-Synchronisationsprimitive

QMutex ist die Basisklasse zur Durchsetzung der gegenseitigen Ausschließung. Ein Thread sperrt einen Mutex, um Zugriff auf eine gemeinsam genutzte Ressource zu erhalten. Versucht ein zweiter Thread, den Mutex zu sperren, während dieser bereits gesperrt ist, wird der zweite Thread in den Ruhezustand versetzt, bis der erste Thread seine Aufgabe abgeschlossen und den Mutex entsperrt hat.

QReadWriteLock ähnelt dem „ QMutex “, unterscheidet jedoch zwischen „Lese-“ und „Schreibzugriff“. Wenn auf eine Datenmenge nicht geschrieben wird, ist es sicher, wenn mehrere Threads gleichzeitig daraus lesen. Ein „ QMutex “ zwingt mehrere Leser dazu, sich beim Lesen gemeinsam genutzter Daten abzuwechseln, während ein „ QReadWriteLock “ gleichzeitiges Lesen zulässt und so die Parallelität verbessert.

QSemaphore ist eine Verallgemeinerung von „ QMutex “, die eine bestimmte Anzahl identischer Ressourcen schützt. Im Gegensatz dazu schützt ein „ QMutex “ genau eine Ressource. Das Beispiel „Producer und Consumer mit Semaphoren“ zeigt eine typische Anwendung von Semaphoren: die Synchronisierung des Zugriffs auf einen Ringpuffer zwischen einem Produzenten und einem Konsumenten.

QWaitCondition „ QWaitCondition “ synchronisiert Threads nicht durch gegenseitigen Ausschluss, sondern durch die Bereitstellung einer Bedingungsvariablen. Während die anderen Primitiven Threads warten lassen, bis eine Ressource freigegeben wird, lässt „ “ 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 zu wecken, oder „ wakeAll()“, um alle gleichzeitig zu wecken. Das Beispiel „Producer and Consumer using Wait Conditions“ zeigt, wie man das Produzenten-Konsumenten-Problem mit ` QWaitCondition ` anstelle von ` QSemaphore` löst.

Hinweis: Die Synchronisationsklassenvon Qt setzen die Verwendung korrekt ausgerichteter Zeiger voraus. So können Sie beispielsweise mit MSVC keine gepackten Klassen verwenden.

Diese Synchronisationsklassen können verwendet werden, um eine Methode threadsicher zu machen. Dies geht jedoch mit Leistungseinbußen einher, weshalb die meisten Qt-Methoden nicht threadsicher sind.

Risiken

Wenn ein Thread eine Ressource sperrt, diese aber nicht wieder freigibt, kann die Anwendung einfrieren, da die Ressource für andere Threads dauerhaft nicht mehr verfügbar ist. Dies kann beispielsweise passieren, wenn eine Ausnahme ausgelöst wird und die aktuelle Funktion gezwungen wird, zurückzukehren, ohne ihre Sperre aufzuheben.

Ein weiteres ähnliches Szenario ist ein Deadlock. Angenommen, Thread A wartet darauf, dass Thread B eine Ressource freigibt. Wenn Thread B wiederum darauf wartet, dass Thread A eine andere Ressource freigibt, werden beide Threads unendlich lange warten, sodass die Anwendung einfriert.

Hilfsklassen

QMutexLocker, ` QReadLocker ` und ` QWriteLocker ` sind Komfortklassen, die die Verwendung von ` QMutex ` und ` QReadWriteLock` vereinfachen. Sie sperren eine Ressource bei ihrer Erstellung und geben sie automatisch frei, wenn sie zerstört werden. Sie dienen dazu, Code zu vereinfachen, der ` QMutex ` und ` QReadWriteLock` verwendet, und verringern so die Wahrscheinlichkeit, dass eine Ressource versehentlich dauerhaft gesperrt wird.

Event-Warteschlangen auf hoher Ebene

Das Ereignissystem von Qt ist sehr nützlich für die Kommunikation zwischen Threads. Jeder Thread kann über eine eigene Ereignisschleife verfügen. Um einen Slot (oder eine beliebige „ invokable “-Methode) in einem anderen Thread aufzurufen, platzieren Sie diesen Aufruf in der Ereignisschleife des Ziel-Threads. Dadurch kann der Ziel-Thread seine aktuelle Aufgabe abschließen, bevor der Slot ausgeführt wird, während der ursprüngliche Thread parallel weiterläuft.

Um einen Aufruf in eine Ereignisschleife einzufügen, erstellen Sie eine Signal-Slot -Verbindung mit Warteschlange. Sobald das Signal ausgesendet wird, werden seine Argumente vom Ereignissystem erfasst. Der Thread, in dem sich der Signalempfänger lives in befindet, führt dann den Slot aus. Alternativ können Sie QMetaObject::invokeMethod() aufrufen, um denselben 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.

Im Gegensatz zur Verwendung von Low-Level-Primitiven besteht bei der Nutzung des Ereignissystems zur Thread-Synchronisation keine Gefahr von Deadlocks. Das Ereignissystem erzwingt jedoch keine gegenseitige Ausschließung. Wenn aufrufbare Methoden auf gemeinsam genutzte Daten zugreifen, müssen sie weiterhin mit Low-Level-Primitiven geschützt werden.

Dennoch bietet das Qt-Ereignissystem zusammen mit implizit gemeinsam genutzten Datenstrukturen eine Alternative zur herkömmlichen Thread-Sperrung. Wenn ausschließlich Signale und Slots verwendet werden und keine Variablen zwischen Threads gemeinsam genutzt werden, kann ein Multithread-Programm gänzlich auf Low-Level-Primitive verzichten.

Siehe auch „ QThread::exec“ () und „Threads und QObjects“.

© 2026 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.