Problem mit PyQt und Thread Modul

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Shaldy
User
Beiträge: 123
Registriert: Sonntag 2. März 2008, 22:49

Hey, zusammen!

Ich möchte in meinem QtProgramm einen zweiten Thread starten und mache das ganz primitiv über thread.start_new_thread().
Der Thread läuft auch tadellos (in einer Endlosschleife wird alle paar Sekunden etwas abgefragt), aber sobald die Abfrage stimmt (im Normalfall ist die Abfrage immer False) rufe ich eine QMessageBox auf. Die wird auch angezeigt, aber dann gibt mir die Konsole folgendes aus:

Code: Alles auswählen

QObject::startTimer: timers cannot be started from another thread
QApplication: object event filter cannot be in a different thread
Diese Ausgabe kommt dann ca. 30 Mal hintereinander.
Liegt das daran, dass ich die QApplication in dem anderen Thread gestartet habe?
Wenn ich den Code in IDLE ausführe, wird diese Meldung nicht angezeigt.

Danke.
Dies ist keine Signatur!
lunar

@Shaldy: Qt hat wie jedes andere GUI-Toolkit strenge Regeln und Richtlinien im Bezug auf Threads und Objekte. Unter anderem besagt eine dieser Regeln, dass jegliche von QWidget abgeleitete Klasse (so auch QMessageBox) nicht "reentrant" [1] ist. Daraus folgt unmittelbar, dass Du jegliche von QWidget abgeleitete Klasse nur und ausschließlich im Hauptthread der Anwendung, in dem das QApplication-Objekt erzeugt wurde, erzeugen und verwenden kannst. Jeglicher Zugriff auf GUI-Funktionalität von einem anderen Thread heraus ist schlicht unmöglich.

Dementsprechend musst die die QMessageBox im Hauptthread erzeugen, und zur Erzeugung dieses Dialogs eine entsprechende Kommunikation zwischen Haupthread und Arbeitsthread implementieren. Da Dir offenbar nicht mal klar ist, welche Teile der Qt-API reentrant und/oder threadsicher sind, wird diese Aufgabe für Dich wohl keineswegs trivial.

Es wäre daher dringend anzuraten, dass Du erst einmal die Thread Support in Qt liest und verstehst, insbesondere hinsichtlich der Einschränkungen, denen Qt in Hinsicht auf Threadsicherheit und "Reentrancy" unterliegt.

[1] Dieser Begriff stammt aus der Qt-Dokumentation, und bezeichnet Objekte oder Methoden, die nicht aus mehreren Threads heraus verwendet werden. Eine angemessene deutsche Übersetzung fällt mir gerade nicht ein.
BlackJack

@lunar: Ich kenne "reentrant" als "ablaufinvariant". Ist aber sicher kein Wort, das man häufig hört. :-)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

lunar hat geschrieben:[1] Dieser Begriff stammt aus der Qt-Dokumentation, und bezeichnet Objekte oder Methoden, die nicht aus mehreren Threads heraus verwendet werden. Eine angemessene deutsche Übersetzung fällt mir gerade nicht ein.
Dieser Begriff ist aber auch über Qt hinaus in Verwendung, ich habe ihn schon mehrmals in Verbindung mit C-Libraries gesehen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Shaldy
User
Beiträge: 123
Registriert: Sonntag 2. März 2008, 22:49

Danke für die Antwort.
Hab das Problem in den Griff bekommen und werde mich dann mal durch die Doku wurschteln.
Dies ist keine Signatur!
lunar

@BlackJack: Danke :) „Ablaufinvariant“ werde ich mir merken ... lieber lasse ich andere nochmal nach der Bedeutung dieses Worts fragen als das ich meine Beiträge mit Anglizismen verunziere ;)

@Leonidas: Im Kapitel Reentrancy and Thread-Safety steht allerdings:
Terminology in the multithreading domain isn't entirely standardized. POSIX uses definitions of reentrant and thread-safe that are somewhat different for its C APIs. When using other object-oriented C++ class libraries with Qt, be sure the definitions are understood.
Offenbar ist der Begriff nicht vollkommen eindeutig definiert, und bezeichnet je nach Bibliothek etwas unterschiedliches. Ich kenne ihn zwar nur in der Bedeutung, in der er auch in der Qt-Dokumentation Verwendung findet, aber um Missverständnissen vorzubeugen, habe ich nochmal explizit darauf hingewiesen, dass ich mich auf die Dokumentation von Qt beziehe.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Qt vermischt hier leider Reentrancy von Klassen mit Reentrancy der Methoden der Exemplare, was nicht gerade zum Verständnis beträgt und Verwirrung in Bezug auf Threadsicherheit stiftet. Eine ablaufinvariante Funktion, die beiden Kriterien (signal- und threadreentrant) gerecht wird, ist auch threadsafe.
Für Klassen, die reentrant sind, sollte das auch gelten, damit werden allerdings nicht die Methoden automatisch reentrant. (NB: malloc gilt als threadreentrant, nicht jedoch als signalreentrant - hier müsste mal schauen, wie es um 'new' und den Konstruktoraufruf in C++ steht und ob nach obigen Kriterien eine reentrante Klasse überhaupt geben kann.)

Mit der Lesart der Threadreentrancy sollte es ablaufinvariante Klassen geben (ala Qt-Doku), stellt sich nun die nächste Frage: Was ist der Ablauf einer Klasse? Die Instanziierung? Ablauf aller Methoden?
Ist es Ersteres, sollte eine ablaufinvariante Klasse auch threadsafe sein (da die Instanziierung zu beliebig vielen "eigenständigen" Exemplaren im jeweiligen Threadkontext führt). Meint man allerdings Zweiteres steht die Begrifflichkeit der ablaufinvarianten Klasse erneut zur Disposition, da eine Methode den Klassenkontext statisch und threadkontextabhängig mitführt und damit eine Methode per definitionem nicht reentrant sein kann.
Diese Unterscheidung trifft die Qt-Doku leider nicht.

Zum Glück liefern Sie Ihre Lesart der Begriffe mit:
* A thread-safe function can be called simultaneously from multiple threads, even when the invocations use shared data, because all references to the shared data are serialized.
* A reentrant function can also be called simultaneously from multiple threads, but only if each invocation uses its own data.
trotzdem bleibt dies
Hence, a thread-safe function is always reentrant, but a reentrant function is not always thread-safe.
ein Fehlschluss, da sich die Einbeziehung von "shared data" in der thread-safe-Definition und die Einbeziehung von "its own data" der reentrant-Definition ausschließen. Threadsafe bedeutet nicht automatisch reentrant.

Just my 2 cents
lunar

Du hast da meines Erachtens einiges missverstanden. Threadsicherheit im Sinne von Qt impliziert sehr wohl auch Ablaufinvarianz. In den Definitionen der von Dir zitierten Sätze schließt sich nichts aus, Du musst die Sätze schon korrekt übersetzen:

Die Definition von "reentrant" lautet übersetzt: "Eine ablaufinvariante Funktion kann gleichzeitig aus mehreren Threads heraus aufgerufen werden, sofern jeder Aufruf seine eigenen Daten benutzt." Somit bedeutet Ablaufinvarianz schlicht, dass eine Funktion überhaupt von mehreren Threads gleichzeitig aufgerufen werden kann, auch wenn sie nur thread-private Daten nutzt. Eine ablaufinvariante Funktion kann dagegen überhaupt gar nicht von mehreren Threads aus aufgerufen werden, sondern nur von einem einzigen. Die Definition von "thread-sicher" erweitert nun die Definition von Ablaufinvarianz: "Eine thread-sichere Funktion kann gleichzeitig aus von mehreren Threads heraus aufgerufen werden, auch wenn die Aufrufe geteilte Daten verwenden, da jeder Zugriff auf die geteilten Daten serialisiert wird." Man könnte thread-sicher auch definieren als "selbst mit geteilten Daten ablaufinvariant", nicht mehr und nicht weniger.

Im Übrigen sehe ich auch kein Problem mit der Anwendung dieser Begriffe auf Klassen. Eine Klasse ist schlicht und einfach dann ablaufinvariant oder thread-sicher), wenn alle Methoden (inklusive Konstruktoren und Destruktoren) dieser Klasse ablaufinvariant beziehungsweise thread-sicher sind. Daraus folgt, dass Exemplare ablaufinvarianter Klassen nur von einem einem einzigen Thread benutzt werden dürfen, da nur in diesem Fall die Attribute des Exemplars als thread-privat gelten können. Wird ein Exemplar dagegen ohne Serialisierung von mehreren Threads aus gleichzeitig verwendet, müssen die Methoden allesamt thread-sicher sein, da Exemplarattribute dann als geteilte Daten gelten müssen. Klassenattribute sind übrigens immer geteilt, da Klassen an keinen Thread gebunden sind.

Nach diesen Definitionen sind folglich QWidget und all dessen Kindklassen nicht einmal ablaufinvariant, da die Methoden dieser Klassen mit dem nicht-ablaufinvarianten Fenstersystem interagieren und demnach keinesfalls ablaufinvariant sein können. QString dagegen ist ablaufinvariant. Mehrere Exemplare dieser Klassen können in verschiedenen Threads verwendet werden, aber ein Exemplar darf nicht aus mehreren Threads heraus benutzt werden, da Schreibzugriffe auf den internen Speicher sinnvollerweise nicht serialisiert werden. Eine thread-sichere Klasse dagegen wäre (logischerweise) QMutex. Diese Klasse arbeitet zwar mit geteilten Daten, serialisiert die Zugriffe aber, so dass ein Exemplar von mehreren Threads verwendet werden kann (andernfalls wäre QMutex natürlich reichlich sinnlos). Thread-sichere Klassen gibt es in Qt verständlicherweise nicht viele, aber einige essentielle Methoden (e.g. "QObject::connect()") sind thread-sicher. Nur deswegen funktionieren Signale und Slots überhaupt über Thread-Grenzen hinweg.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@lunar:
Du darfst mir ruhig zutrauen, dass ich erfassen kann, was da steht. Richtiger wirds deswegen aber noch nicht. Grundproblem ist, dass Qt hier eine sehr eigenwillige Vorstellung von Ablaufinvarianz hat und diese nicht sauber definiert, sondern einmal von "reentrant functions" spricht um im nächsten Atemzug bei "reentrant classes" zu landen. Im Übrigen widerspricht Qt's reentrant-"Definition":
A reentrant function can also be called simultaneously from multiple threads, but only if each invocation uses its own data.
nicht der gängigen:
A reentrant function can be safely called simultaneously or before its previous invocation has been completed.
Der Nachsatz "if each invocation uses its own data." ist allerdings irreführend, da dies eigentlich eine harte Forderung gegenüber ablauf- oder eintrittsinvarianten Funktionen ist (Auch thread-lokal, Ablaufinvarianz ist nicht nur ein Nebenläufigkeitsproblem sondern auch bei Interrupts/Singalhandlern oder Rekursionen von Bedeutung). Da sie aber eine exakte eigene Definition schuldig bleiben, muss vorallem dieser Satz verwirren:
Hence, a thread-safe function is always reentrant, but a reentrant function is not always thread-safe.
Nach klassischer Definition ist es genau umgekehrt, eine Funktion, die allen Kriterien der Ablaufinvarianz genügt, ist threadsicher, Threadsicherheit führt aber keineswegs automatisch zur Ablaufinvarianz.

Was Qt unter Ablaufinvarianz von Klassen versteht, wird mit diesem Satz deutlich:
By extension, a class is said to be reentrant if its member functions can be called safely from multiple threads, as long as each thread uses a different instance of the class.
Das hat allerdings wenig gemein mit der üblichen Vorstellung des Begriffes sondern erinnert eher an einen Kunstgriff ala Thread-local storage.

Ich akzeptiere natürlich, dass Qt eine eigene Vorstellung der Begrifflichkeit hat. Bregrüßenswert wäre allerdings, wenn sie diese sauberer definieren und gegen andere Bedeutungen abgrenzen würden. Das ist keineswegs mit einem Nachsatz am Ende des Dokumentes getan, zumal es eine gewisse Standardisierung durchaus gibt (IEEE).
lunar

@jerch: Ich traue Dir durchaus zu, den Text zu erfassen, alles andere wäre Anmaßung. Einem fundamentalen Missverständnis unterliegst Du imho dennoch: Es geht hier um die Qt-Dokumentation! Diese Dokumentation erhebt keinen Anspruch auf theoretische und wissenschaftliche Vollständigkeit oder Korrektheit, sondern nur auf praktische Relevanz für das Qt-Toolkit. Natürlich gibt es andere, "richtigere(TM)" Definitionen dieser Begriffe, nur ist das hier vollkommen ohne Belang, denn wichtig ist nicht, ob die Definitionen der Qt-Dokumentation richtig oder sauber sind, sondern ob sie exakt genug sind und ganz unmittelbar beim Verständnis der Eigenschaften der Qt-Klassen helfen.

Und zumindest für meinen Teil ist das durchaus gelungen, mich verwirrt da nichts. Mir persönlich ist anhand der in diesem Text gegebenen Definitionen vollkommen klar, was bei im Sinne von Qt ablaufinvarianten Funktionen, Methoden oder Klassen erlaubt ist, und welche zusätzliche Garantie Threadsicherheit im Sinne von Qt gibt.

Insofern werde ich aus Deinen Beiträgen nicht schlau. Wenn Du keine Probleme mit dem Verständnis des Texts der Qt-Dokumentation hast, was ist dann der Punkt Deiner Beiträge? Doch nur die Beschwerde darüber, dass Qt eigene, der klassischen Deutung widersprechende Definitionen der Begriffe gibt. Das ist natürlich im Sinne einer klaren Begriffswelt reichlich unschön, aber meines Erachtens machst Du hier aus einer doch recht zierlichen Mücke vielleicht nicht gerade einen Elefanten, aber zumindest doch ein veritables Nashorn. ;)

Letztlich geht ja vor allem darum, dass die Qt-Dokumentation verständlich und zielführend ist. Nun wird die Masse der Entwickler die „richtige“ Bedeutung der Begriffe Threadsicherheit und Ablaufinvarianz ad hoc nicht rekapitulieren können, ich wette mit Dir, dass auch Du die exakte Bedeutung vor dem Verfassen Deiner Beiträge nachgeschlagen hast. In jedem Fall muss man also erklären, wie diese Begriffe zu verstehen sind. Dann aber kann man sich auf Definitionen verwenden, die im Kontext der dokumentierten Bibliothek unmittelbar sinnvoll sind, und muss keine vollständige und korrekte theoretische Abhandlung verfassen. Die Qt-Dokumentation stellt Pragmatismus eben über theoretische Korrektheit. Man kann sich nun darüber streiten, ob das richtig(TM) ist, ich persönlich finde einfach nur, dass die Qt-Dokumentation gut verständlich und zielführend ist, und vieles andere in diesen Punkt weit hinter sich lässt.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Kernelprogrammierer würden die Definition wahrscheinlich in der Luft zerreissen, da der logische Zusammenhang zwischen Threadsicherheit und Ablaufinvarianz völlig verdreht wird. Auf diesen Sachverhalt wollte ich hinweisen, nicht mehr und nicht weniger.

In puncto Nashorn verweise ich auf die Postlänge ;)
lunar

So gesehen habe ich darauf hingewiesen, dass hier nicht um den Kernel geht ;)
Antworten