GTK und Threads / Threading, Gobject !?

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Tundle:

Das mit dem Gui-Zugriff aus nur einem Thread heraus verstehe ich anders. Vielmehr scheint der zeitgleiche Zugriff über mehrere Threads nicht sicher zu sein, was mit threads_enter/leave eben abgesichert werden soll (also prinzipiell möglich ist).
Siehe hierzu: http://research.operationaldynamics.com ... eness.html
und http://research.operationaldynamics.com ... -java.html

(Von Qt her war ich davon ausgegangen, daß signal-emitting thread-safe ist, was in Gtk eben nicht der Fall ist.)

Ich bin mir des Windows-Problemes bewußt, nur spielt Gtk auf Windows für mich keine Rolle. Trotzdem hab ich mal das Script angepaßt und hätte gern mal eine Stellungnahme von Dir hierzu.

Code

Das Signal wird jetzt per idle_add() abgesetzt, ein direkter Threadzugriff erfolgt nicht mehr. Verbesserungsvorschläge?

Grüße, Jerch
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

@whaeva:
Wo das im Code steht, sagt doch rein gar nichts darüber aus, in welchem Thread das ausgeführt wird. Der Code hier wirft einen `AssertionError`, was er aber nicht tun dürfte, wenn der Handler im gleichen Thread aufgerufen würde, in dem auch die Mainloop läuft.

@jerch:
Das kommt eben auf die Plattform an. Unter X11 darf man mit mehreren Threads zugreifen, nur eben nicht gleichzeitig. Unter Windows darf man aber wohl überhaupt nicht mit mehreren Threads zugreifen. Ob man Windows unterstützen mag oder nicht, muss jeder mit sich selbst ausmachen. Ich persönlich mag jedoch schon, dass meine Programme unter Windows laufen und habe auch keine Lust, versehentlich einen Deadlock einzubauen, weshalb ich `idle_add()` benutze.

Zum Code: Sieht bis auf die ``on_window_destroy``-Methode gut aus. In `on_window_destroy()` ist jedoch die Hauptschleife von GTK+ unterbrochen und es wird gewartet, bis der Thread beendet wird, dh in der Zeit, in der der Thread noch lebt, wird die GUI nicht neu gezeichnet und das Fenster verschwindet auch nicht, wodurch sich die GUI eben "unschön" anfühlen könnte, da sie nicht gleich reagiert. Und das ``data=None`` in den Handlern ist auch überflüssig und eher irreführend. In C wird den Handlern immer noch ein Zeiger mit Userdaten übergeben (der auch "NULL" sein kann), in Python können aber noch beliebig viele Argumente folgen, nämlich die, die man beim Verbinden des Signals angegeben hat. Hat man da keine angegeben, wird auch kein weiteres übergeben.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Danke Trundle.
Der Kritik an dem data=None nehm ich mich mal nicht an, war nur copy&paste von whaeva (zugegebenermaßen ohne Verstand lol).

ok join hinter main_quit, vllt. mit timeout. Wenn der join hier nicht mehr gelingt, liegt eh ein problem mit dem thread vor :(, dann kann nur noch das OS helfen.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Ich habe die Diskussion jetzt nur überflogen, aber hier noch meine persönliche Erfahrung zum Thema GTK und Threads unter Windows.

Threads und GTK funktionieren auch unter Windows problemlos, solange man nicht aus mehreren Threads auf die GUI Elemente zugreifen will. Und das willst du ja auch nicht, soweit ich das mitbekommen habe.

Um Threads im GTK Mainloop nutzen zu können, reicht ein

Code: Alles auswählen

gobject.threads_init()
Da muss man dann auch nicht mit dem thread_enter und thread_leave arbeiten, sondern kann die Standard Threads mit den Standard Lock Funktionen nutzen, falls man in kritische Bereiche kommt.

Hier ist ein Beispiel (allerdings funktioniert die Loggerklasse so nicht)
http://www.python-forum.de/topic-17844.html
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Manchmal ist es hilfreich, Diskussionen nicht nur zu überfliegen. Es gibt die Dokumentation von GDK, die klar sagt, dass man unter Windows besser nicht versuchen sollte, von mehreren Threads aus auf GDK-Funktionen zuzugreifen. Und zwar grundsätzlich, nicht nur nicht gleichzeitig (was hier auch von Owen Taylor bestätigt wird: "Using GTK+ from multiple threads on Windows doesn't work at all, and would require substantial work and complexity to fix"). Das gefährliche an Threads und GUI ist, dass Fehler nicht deterministisch auftreten, wodurch das mit der eigenen praktischen Erfahrung immer so eine Sache ist.

Desweiteren ist mir nicht klar, wie du nur mit eigenen Locks verhindern willst, dass nicht gleichzeitig auf die GUI zugegriffen wird. Damit verhinderst du, dass dein eigener Code in den Threads vllt nicht gleichzeitig auf die GUI zugreift. Aber dabei wird doch die Mainloop von GTK+ nicht unterbrochen. Das heißt, es wird auf Events reagiert, Signale werden ausgelöst, etc. pp. Und jetzt soll auf einmal GTK+ nicht mehr von sich aus auf die GUI zugreifen, nur weil du einen eigenen Lock hast, von dem GTK+ überhaupt nichts weiß?
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Trundle hat geschrieben:Es gibt die Dokumentation von GDK, die klar sagt, dass man unter Windows besser nicht versuchen sollte, von mehreren Threads aus auf GDK-Funktionen zuzugreifen.
Ja richtig, habe ich so auch gesagt.
Er will in einem Thread Daten aktualisieren und falls sich was ändert, will er diese in der GUI anzeigen bzw. updaten. Sehe da keine Notwendigkeit in mehreren Threads auf die GUI bzw. auf GTK zuzugreifen. Das Absetzen eines Signals aus einem Thread ist ja kein direkter Zugriff auf die GUI.
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

ice2k3 hat geschrieben:
Trundle hat geschrieben:Es gibt die Dokumentation von GDK, die klar sagt, dass man unter Windows besser nicht versuchen sollte, von mehreren Threads aus auf GDK-Funktionen zuzugreifen.
Ja richtig, habe ich so auch gesagt.
Dann verstehe ich dein Posting einfach nicht. Du schreibst etwas von Locks, warum aber Locks, wenn man doch nicht auf die GUI zugreift? Zumal das mit den eigenen Locks wie gesagt nicht funktioniert.
ice2k3 hat geschrieben:Er will in einem Thread Daten aktualisieren und falls sich was ändert, will er diese in der GUI anzeigen bzw. updaten. Sehe da keine Notwendigkeit in mehreren Threads auf die GUI bzw. auf GTK zuzugreifen. Das Absetzen eines Signals aus einem Thread ist ja kein direkter Zugriff auf die GUI.
Dann dürfen die Signal-Handler aber auch nicht auf die GUI zugreifen.

Ich habe mal deinen Logger als Beispiel genommen (das Tray-Icon habe ich entfernt, stattdessen wird das Fenster immer angezeigt und den Code zum Überwachen der Threads habe ich auch entfernt). Einmal mit einem `threading.RLock()`, einmal mit einem Signal. Zumindest auf meiner Maschine produzieren beide Versionen mit an Sicherheit grenzender Wahrscheinlichkeit einen Segfault.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Deine Beispiele sollen ja wohl ein Witz sein oder??
Wenn du einen Textbuffer mit while True Prints überflutest, ist ja klar, dass das nach ein paar Sekunden abkackt.

Folgender leicht modifizierter Code mit den Signalen funktioniert problemlos.
http://paste.pocoo.org/show/107434/

Und dass das zweite Beispiel so nicht funktioniert, ist mir auch klar ohne zu testen. Du greifst aus dem zweiten Thread auf GTK Elemente zu (in dem du den Textbuffer beschreibst). Und genau das soll man ja laut Doku nicht machen :!:

Ich weiß, dass das in meinem alten Beispiel auch so ist, darum habe ich aber erwähnt, dass das mit dem Logger nicht funktioniert. Meine Alternative war, dass ich eine Liste mit den Prints gefüllt habe und die Liste regelmäßig in den Textbuffer geschrieben habe. Und genau die Liste muss dann mit einem Lock geschützt werden. Aber vielleicht setze ich jetzt auch dein Signal-Handler Beispiel ein. Schein echt gut zu funktionieren, wenn man es richtig einsetzt...
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Nein ice2k3, Dein letztes Bsp. crasht mit dem Hinweis eines ungültigen Iterators auf den Textbuffer. Schau Dir bitte mal die Links an, welche ich weiter oben gepostet habe. Hier ist die Rede davon, das der Zugriff von versch. Threads möglich ist, hierfür aber gdk_threads_set_lock_functions() "gefixt" werden muß, um die Locks vollwertig zu erhalten. Desweiteren warnen die gtk-Leute vor dem Benutzen anderer Lockmechanismen als dem Gtk-eigenen, da hier wiederum Seiteneffkte nicht ausgeschlossen sind. So, das gilt auch nur für unixoide Systeme, unter Windows ist das Gtk-Locking gänzlich unbrauchbar.

Was spricht eigentlich gegen meinen Vorschlag, emit() via idle_add() zum MainThread durchtröpfeln zu lassen und so "normale" Signale absetzen zu können?
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Nein, meine Beispiele sind kein Witz. Vielmehr ist deine Aussage, dass dein Code "problemlos" funktioniere, ein Witz. Aber im Grunde bin ich ja froh, denn das war genau das, was ich damit zeigen wollte (ich hab sogar noch überlegt, ob ich das Beispiel auch hinzufüge). Dass dein Code funktioniert, ist reiner Zufall bzw. Glücksspiel. Und genau das meinte ich damit, dass Probleme mit Threads nicht deterministisch auftreten und dass das mit der eigenen praktischen Erfahrung so eine Sache ist.

Dass mein Code wegen überfluten segfaultet, stimmt, nicht aber, dass das klar ist, weil ich den Textbuffer überflute. GTK+ (und nicht nur das, sondern eigentlich jedes Toolkit) verträgt es nicht, wenn man gleichzeitig aus mehreren Threads auf die GUI zugreift. Soweit sind wir uns hoffentlich alle einig. Dadurch, dass ich jetzt - wie du so schön sagst - "überflute", tritt aber mit sehr hoher Wahrscheinlichkeit der Fall ein, dass eben zwei Threads (mein "print-Thread" und eben die GTK+-Hauptschleife) auf die GUI zugreifen. Du hingegen sorgst mit deinem ``sleep(0.1)`` dafür, dass es sehr unwahrscheinlich ist, dass dieser Fall eintritt. Aber er ist keinesfalls ausgeschlossen. Tatsächlich bekomme ich auch mit deinem "problemlos funktionierenden" Code jedes Mal einen Segfault hin, ich muss das eben nur ein wenig provozieren -- indem ich den Text markiere, die Größe vom Fenster ändere, beim TextView scrolle etc.

Dass es tatsächlich an den Threads und nicht am "Überfluten" liegt, lässt sich auch sehr einfach demonstrieren, indem man das ``self.emit('write', text)`` durch ein ``gobject.idle_add(self.emit, 'write', text)`` austauscht.


Edit: @jerch: Dagegen spricht nichts, und ich persönlich würde es wohl auch so lösen.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

jerch hat geschrieben:Was spricht eigentlich gegen meinen Vorschlag, emit() via idle_add() zum MainThread durchtröpfeln zu lassen und so "normale" Signale absetzen zu können?
Genau so habe ich mir das ja vorgestellt.

Aber ganz egal wie ich das Beispiel ändere, die write_handler Methode wird immer von den Threads und nie vom Main-Thread aufgerufen.
Wie müsste das in meinem Beispiel dann konkret aussehen?
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Warum wollt ihr eigentlich unbedingt mit einem Signal arbeiten?
Als "Signal" kann man doch einfach eine Methode der GUI Klasse aufrufen, die auf die GUI nicht zugreift, und die dann eine Methode mit ``gobject.idle_add`` aufruft um die GUI zu ändern.

Folgendes Beispiel funktioniert bei mir:
http://paste.pocoo.org/show/107525/
(Die 2. Methode soll nur demonstrieren, dass das auch mit normalen Methoden funktioniert und nicht nur mit der stdout Umleitung auf die ``write`` Methode)

Edit:
Warum im Thread nicht einfach so??

Code: Alles auswählen

gobject.idle_add(my_gui_class.change_label, new_label)
Der Zwischenschritt über die extra Methode benötigt man ja gar nicht...
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Wie Du das mit dem idle_add() umsetzt, ist doch eine Frage dessen, was Dein Programm letztendlich machen soll.
Wie ich den OP verstanden hatte, braucht er aus dem Thread heraus verschiedene Manipulationsmöglichkeiten an der GUI. Mit dieser Anforderung ist es doch naheliegend, dem Thread Signale beizubringen.
Braucht der Thread hingegen nur eins/zwei Interaktionsmöglichkeiten mit der GUI, bläßt das nur das Programm unnötig auf. Dann rufst Du eben Methoden direkt auf, mußt aber sicherstellen, das in den Methoden keine "globalen" (GUI oder andere) oder beschränkten Ressourcen (Devicezugriff) manipuliert werden oder diese Zugriffe aus dem Thread heraus locken. Gut, das Locking funktioniert nicht in Windows im Gtk-Falle, daher der Umweg über idle_add().
Der Zwischenschritt über die extra Methode benötigt man ja gar nicht...
Die "Zusatzmethode" stellt doch nur eine Art "gateway"-Funktion zur Verfügung, um den idle_add() Aufruf nicht jedesmal neu setzen zu müssen. Du machst es ja auch so mit der Methode insert_text().

Übrigens hat Dein Programm ein designtechnisches Problem:
Schließt man das Fenster, so fährt Gtk runter während der Thread weiterhin versucht, die Ausgabe nach logger() abzusetzen, was auch schön mit einer Exception quittiert wird. Das sollte sich aber mit einem callback lösen lassen.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

jerch hat geschrieben:Dann rufst Du eben Methoden direkt auf, mußt aber sicherstellen, das in den Methoden keine "globalen" (GUI oder andere) oder beschränkten Ressourcen (Devicezugriff) manipuliert werden oder diese Zugriffe aus dem Thread heraus locken.
Aber wenn ich die Methoden über idle_add aufrufe, kann ich ja auf die GUI zugreifen. Die Methode wird ja dann vom Mainthread aufgerufen (wie das Beispiel ja auch zeigt).
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Aber wenn ich die Methoden über idle_add aufrufe, kann ich ja auf die GUI zugreifen. Die Methode wird ja dann vom Mainthread aufgerufen (wie das Beispiel ja auch zeigt).
Nein.
Mit idle_add() greifst Du weder auf die GUI zu noch rufst DU diese Methoden direkt auf. Bildlich gesprochen weist Du gdk an, doch mal bei dieser Methode vorbeizuschauen. Daher dann auch der MainThread-Zugriff.

Das steht in keinem Widerspruch zu meinem Vorpost.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Ja, das ist mir klar.

Aber ich verstehe immer noch nicht, warum Signale eingesetzt werden sollen. Wenn man mit ``idle_add`` eine emit Funktion aufruft, kann man doch gleich die Methode direkt aufrufen, die mit dem Signal aufgerufen werden würde.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Wir reden offensichtlich aneinander vorbei.
Aber ich verstehe immer noch nicht, warum Signale eingesetzt werden sollen. Wenn man mit ``idle_add`` eine emit Funktion aufruft, kann man doch gleich die Methode direkt aufrufen, die mit dem Signal aufgerufen werden würde.
Die Frage zielt eher in Richtung generelle Sinnhaftigkeit von Signalen, die Dir sicherlich klar ist. Womit auch die Frage beantwortet sein sollte. (Ich habe das auch schon mehrfach in diesem Forumthread angerissen.)
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Letztlich lässt sich das alles ohne konkreten Code des Threads doch gar nicht sagen. Die ursprüngliche Idee ist es ja, serielle Geräte anzusteuern. Zumindest nach meiner Vorstellung muss aber Code, der ein Gerät steuert, nichts über die GUI wissen.

Und dann ist eben die Frage, wie man diesen allgemeinen, von der GUI unabhängigen Code jetzt in die GUI einbindet. Eine Möglichkeit wären eben einfach Callbacks (in oder ohne Verbindung mit einem Thread, mit oder ohne dass der Aufrufer den Callback indirekt durch `gobject.idle_add()` aufruft (man kann ja auch einfach `gobject.idle_add` als Callback übergeben)), eine andere Möglichkeit wären sicherlich aber auch Signale. Signale sind in GUIs nichts ungewöhnliches und man könnte damit dann solche Sachen machen, dass man ein Logging-Fenster hat, das einfach nur die rohen Daten anzeigt, die empfangen werden und dass man ein Fenster hat, in dem diese Daten dann grafisch aufbereitet werden. Mit Signalen kann man nun eben einfach das gleiche Objekt mehrfach unabhängig voneinander verwenden, was IMHO recht elegant ist, zumal einem die dazu benötigten Dinge zur Verfügung stehen, ohne dass man sie selber implementieren müsste.

Wie genau man es macht, hängt jetzt halt davon ab, was genau man vorhat und der persönliche Geschmack spielt sicherlich auch eine Rolle. Wichtig ist, und darum ging es mir in diesem Thread, dass man mit Threads eben aufpassen muss.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Antworten