Open Source im professionellen Einsatz
Linux-Magazin 07/2010
© Alina Pavlova, 123RFR.com

© Alina Pavlova, 123RFR.com

Workerthreads mit Qt

Eingezäumt

Threads, die Arbeitstiere jeder Qt-Applikation, sorgen für ein reaktives GUI und ein verbessertes Benutzererlebnis - wenn sie die CPU parallel nutzen. Dazu muss der Entwickler sie mit einem Entwurfsmuster erst einmal bändigen, denn andernfalls hören sie nicht auf externe Kommandos.

753

Einem typischen Muster folgend wiederholt ein Workerthread eine ganz bestimmte Aufgabe so lange, bis der Entwickler ihn abbricht. Klassische Anwendung für Workerthreads sind Messwerterfassung, Berechnungen oder Maschinensteuerungen.

Qt erlaubt es seit der Version 4, das bekannte Signal-Slot-Konzept auch über Threadgrenzen hinweg zu nutzen [1]. Das setzt jedoch eine bestehende Eventqueue voraus. Aber genau die erschwert es, Workerthreads umzusetzen, da sie selbst eine Endlosschleife ist und damit jeden Code des Entwicklers blockiert. Abhilfe finden jene, die einen eigenen Workerthread implementieren, der durch Signals und Slots ansprechbar bleibt. Ist der nach modernen Designprinzipien entworfen, lässt er sich nahtlos in eigene Projekte integrieren.

Um Threads in Qt zu implementieren, definieren Entwickler typischerweise eine Klasse, die von »QThread« ableitet. Den eigentlichen Code, der parallel ablaufen soll, implementieren sie in der überschriebenen Methode »QThread::run()«.

Implementierung in Qt

Der so programmierte Thread läuft durch den Aufruf der Methode »QThread::start()« los. Die Klasse »QThread« ist intern so implementiert, dass sie auf jeder Zielplattform von Qt den jeweils nativen Code zur Threadverwaltung nutzt. Der Workerthread führt eine bestimmte Codefolge fortlaufend aus, zum Beispiel in einer While-Schleife.

In Verbindung mit Qt und der Anforderung, weiterhin auf Signale und Slots zu reagieren, entsteht ein Problem: Möchte der Entwickler, dass sein Threadcode auf Signale antwortet, muss er aus der Methode »run()« heraus »exec()« aufrufen. Erst dann startet die Eventloop und der Thread reagiert auf Signale. Da die Qt-Entwickler die Methode »exec()« jedoch selbst als Endlosschleife implementiert haben, ist es nicht unmittelbar möglich, einen Workerthread zu implementieren, der weiterhin auf Signale reagiert.

Ein zusätzliches Problem taucht auf, wenn der Programmierer ein Signal aus einem Thread mit dem Slot eines anderen Thread verbinden will: In Qt besitzt jedes Objekt eine so genannte Threadaffinität [2]. Das bedeutet, dass es immer in dem Thread lebt, in dem sein Konstruktor aufgerufen wurde. Die CPU führt den Code von Slot-Methoden immer im Kontext dieses Thread aus. Entwickler würden erwarten, dass eine per »connect()« verbundene Slot-Methode im Kontext des neuen Thread läuft. Tatsächlich führt Qt den Code der Slot-Methode jedoch im Kontext des Hauptthread aus. Das liegt daran, dass Qt das »QThread«-Objekt, das den neuen Thread repräsentiert, weiterhin im Hauptstrang ausführt.

Doch findige Entwickler wissen sich zu helfen und kennen die Methode »moveToThread()«. Damit vermögen sie die Threadaffinität eines Objektes zu ändern. Aufgrund der internen Implementierung gelingt es aber nur, ein Objekt vom eigenen Thread in einen anderen zu schieben. Eine andere Verwendung verhält sich undefiniert und führt im schlimmsten Fall zu einem Absturz der Anwendung.

Zwei Threads im Gespräch

Seit Qt 4.0 erlaubt die Bibliothek, dass Threads über Signale und Slots kommunizieren und so Daten miteinander austauschen. Dabei müssen Entwickler jedoch eine Besonderheit der »connect()«-Anweisung beachten: Der optionale fünfte Parameter definiert, wie die Eventqueue die Signale an das empfangende Objekt ausliefert. Tabelle 1 listet die gültigen Werte für den Parameter auf.

Tabelle 1: Varianten
von »connect()«

 

fünfter Parameter

Funktion

"Qt::AutoConnection" (Standard)

Liegt das Empfängerobjekt im selben Thread wie das
Senderobjekt, verhält sich der Typ wie "Qt::DirectConnection",
ansonsten wie "Qt::QueuedConnection".

"Qt::DirectConnection"

Umgeht die Eventqueue vollständig. Der "emit()"-Aufruf
verhält sich wie ein normaler Funktionsaufruf. Verbessert die
Performance, wenn die Auslieferung des Signals über die
Eventqueue nicht notwendig ist.

"Qt::QueuedConnection"

Legt das Signal zunächst in der Eventqueue des
Empfängerthreads ab. Beim nächsten Durchlauf verarbeitet
er es dann.

"Qt::BlockingQueuedConnection"

Wie "Qt::QueuedConnection", der "emit()"-Aufruf beim Sender
blockiert jedoch solange, bis Qt den Slot tatsächlich
ausführt.

"Qt::UniqueConnection"

Neu seit Qt 4.6: Wie "Qt::AutoConnection", fügt die
Verbindung jedoch nur dann der Eventqueue hinzu, wenn sie noch
nicht existiert.

"Qt::AutoCompatConnection"

Qt-3-Klassen verwenden diesen Parameter hauptsächlich. Er
sorgt dafür, zusätzliche Warnungen bei bestimmten
Konstellationen auszugeben.

Das Problem liegt im Verhalten der Vorgabe durch »Qt::AutoConnection«: Manchem Entwickler unterläuft der Flüchtigkeitsfehler, Signal-Slot-Verbindungen zwischen Threads nicht explizit mit »Qt::QueuedConnection« zu definieren. Dies bereitet mitunter Probleme: Stellt der Entwickler vom Hauptthread eine Verbindung zu einem anderen Threadobjekt her, liegen sowohl das Sender- als auch das Empfängerobjekt im selben Ablaufstrom. Dies hat zur Folge, dass das Standardverhalten von »connect()« einen direkten Funktionsaufruf implementiert. Dadurch führt Qt den Code der Slot-Methode im Kontext des aufrufenden Thread, also nicht dem Zielthread, aus. Explizit eine »Qt::QueuedConnection« anzufordern löst das Problem.

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Kern-Technik

    Ein ausgeklügeltes Framework verwaltet das Abarbeiten asynchroner Codesequenzen im Linux-Kernel: Kworker-Threads. Was sie sind, wie sie arbeiten und wer die Arbeitstiere für seine Zwecke einspannen darf, erklärt die neueste Folge der Kern-Technik.

  • Sauber eingefädelt

    Der Standard für Threads unter Linux ist heute die Native Posix Threads Library (NPTL). Die Bibliothek überzeugt durch große Kompatibilität zum Standard und hohe Performance. Dieser Artikel untersucht die neue Threading-Engine und zeigt, wie Benutzeranwendungen davon profitieren.

  • Java-Threads

    Seit der ersten Version von Java sind Threads ein fester Bestandteil der Sprache. Das macht vieles einfacher als in anderen Programmiersprachen. Neuere Versionen der Java-Bibliothek bieten darüber hinaus viele nützliche Klassen für Locking und Synchronisierung.

  • C++11

    Die C++11-Reihe beschäftigt sich weiter mit dem Synchronisieren von Threads. Diesmal setzt der Chef-Thread Bedingungsvariablen ein, um die Tätigkeit seiner Mitarbeiter-Threads zu koordinieren.

  • C++11

    Promise und Future erweisen sich als nützliche Neuerungen im C++11-Standard. Das Gespann macht die bisher aufwändige Synchronisation mehrerer Threads einfach und übersichtlich.

comments powered by Disqus

Ausgabe 11/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Stellenmarkt

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.