Bonjour. J'essaye de faire la synthèse de ce que j'ai glané sur la toile à propos de pygtk et les threads. J'essaye donc un programme très simple d'expérimentation. J'ai une classe principale, une qui gère 2 threads et une classe thread proprement dite.
Mon problème : lorsque les threads 1 et 2 sont lancés simultanément, j'ai toujours un seul des deux labels qui est mis à jour (analogie avec une situation XOR).
Je commence à bloquer, je n'ai pas trouvé de documentation vraiment approfondie sur ce sujet. D'avance merci.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from threads_manager import ThreadsManager
import gobject
gobject.threads_init() # <-----
import gtk
class Main:
def __init__(self):
self.window = gtk.Window()
self.button1 = gtk.Button('Start/stop thread 1')
self.button2 = gtk.Button('Start/stop thread 2')
self.labels = []
self.labels.append(gtk.Label('*'))
self.labels.append(gtk.Label('*'))
self.table = gtk.Table()
self.table.attach(self.labels[0], 0, 1, 0, 1, xpadding=10)
self.table.attach(self.button1, 1, 2, 0, 1)
self.table.attach(self.labels[1], 0, 1, 1, 2, xpadding=10)
self.table.attach(self.button2, 1, 2, 1, 2)
self.window.add(self.table)
self.threads_manager = ThreadsManager(self)
self.button1.connect('clicked', self.threads_manager.switch_thread_state, 1)
self.button2.connect('clicked', self.threads_manager.switch_thread_state, 2)
self.window.connect('destroy', self.quit)
self.window.set_position(gtk.WIN_POS_CENTER)
self.window.show_all()
def main(self):
gtk.main()
return 0
def quit(self, widget_window):
# À finir. Informer les threads qu'ils doivent stopper afin de
# quitter proprement ?
gtk.main_quit()
if __name__ == '__main__':
main = Main()
main.main()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import threading
from my_thread import MyThread
class ThreadsManager:
def __init__(self, main):
self.main = main
self.th1 = MyThread(self.main, 1)
self.th2 = MyThread(self.main, 2)
def switch_thread_state(self, widget_button, nb):
if nb == 1:
if self.th1.is_running == True:
self.th1.stop()
else:
self.th1.start()
else: # Therefore nb == 2
if self.th2.is_running == True:
self.th2.stop()
else:
self.th2.start()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import threading, time
import gobject
import gtk
class MyThread(threading.Thread):
def __init__(self, main, nb):
threading.Thread.__init__(self)
self.main = main
self.nb = nb
self.is_running = False
def start(self):
print 'start thread', self.nb
self.is_running = True
while self.is_running == True:
gobject.idle_add(self.update_label) # <-----
time.sleep(0.1)
gtk.main_iteration() # <-----
def stop(self):
print 'stop thread', self.nb
self.is_running = False
def update_label(self):
# Le but de cette fonction est de montrer comment modifier
# un objet gtk dans la boucle principale depuis un thread.
self.main.labels[self.nb-1].set_text(str(time.time()))
# Une solution
Posté par Achille Fouilleul (site web personnel) . Évalué à 1.
[^] # Re: Une solution
Posté par Achille Fouilleul (site web personnel) . Évalué à 1.
- les tests "if x == True" et "while y == False" peuvent être réécrits "if x" et "while not y"
- la classe MyThread pose des problèmes de synchronisation ("race conditions"): il se peut que la valeur de is_running soit fausse si un autre thread intervient par ex.
- j'ai oublié "self.th.join()" avant "self.th = None", pour attendre que le thread ait terminé avant de revenir.
[^] # Re: Une solution
Posté par thor_tue . Évalué à 1.
OK, j'avais hérité MyThread de la classe threading.Thread parce que tous les exemples procédaient ainsi. Bon, j'ai un exemple fonctionnel je vais compulser à fond la doc sur les threads. Merci encore.
[^] # Re: Une solution
Posté par benoar . Évalué à 2.
Tout d'abord, comme Achille l'a fait, pour une simple boucle, on ne sous-classe pas Thread. Ou alors, si tu veux le faire, lis la doc qui dit :
There are two ways to specify the activity: by passing a callable object to the constructor, or by overriding the run() method in a subclass. No other methods (except for the constructor) should be overridden in a subclass. In other words, only override the __init__() and run() methods of this class.
Dans l'exemple que tu donnes, thor_tue, tu n'as simplement jamais créé de threads ! Tout s'éxécutait dans le processus principal, d'où l'exclusivité mutuelle que tu observais …
Cependant, la solution d'Achille n'est pas la bonne non plus : tu utilises bien des threads, mais ils ne servent qu'à attendre 0.1s et lancer la mise à jour des labels … dans le processus principal !
La fonctions gobject.idle_add() sert à faire du « faux » multithread en exécutant une fonction dans le processus principal quand elle a le temps (entre deux mises à jours de widget). C'est pratique quand tu ne veux pas utiliser de threads, mais inutile dans notre cas.
Ensuite, le gtk.main_iteration() c'est vraiment un hack : en général, quand tu l'utilises, c'est que tu as mal fait quelque chose. Ce n'est qu'à utiliser que dans des cas très particuliers.
Les fonctions qui nous seront utiles sont gtk.gdk.threads_enter() et gtk.gdk.threads_leave(). Elles permettent à des threads en dehors du « principal », qui fait tourner la boucle d'évènements, d'aller tripoter la GUI sans interférer avec cette boucle, de manière thread-safe. En bref, elle va « juste » demander un lock à la boucle principale lui permettant de jouer avec la GUI jusqu'à ce qu'elle le relâche. On doit donc minimiser le temps passé entre en enter() et un leave(), car la boucle principale est bloquée pendant ce temps !
En gros, je pense qu'il faut que vous saisissiez bien le principe d'une boucle d'évènements avant de vous lancer là-dedans. Désolé je n'ai pas de liens sous la main.
Voici ma version :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import threading, time
import gobject
import gtk
import gtk.gdk
class MyThread:
def __init__(self, main, nb):
self.main = main
self.nb = nb
self.is_running = False
def start(self):
print 'start thread', self.nb
self.is_running = True
self.th = threading.Thread(target = self.loop)
self.th.start()
def stop(self):
print 'stop thread', self.nb
self.is_running = False
self.th.join()
self.th = None
def loop(self):
while self.is_running == True:
gtk.gdk.threads_enter()
self.update_label()
gtk.gdk.threads_leave()
time.sleep(0.1)
def update_label(self):
# Le but de cette fonction est de montrer comment modifier
# un objet gtk dans la boucle principale depuis un thread.
self.main.labels[self.nb-1].set_text(str(time.time()))
Enfin, comme dis Achille, tu peux toujours avoir un problème de race-condition sur ta variable is_running. Il vaut mieux utiliser des locks ou autre.
Ah oui, au passage, la version d'Achille crée un thread à chaque start(). Si tu ne veux pas ce comportement, il faudra aussi jouer des lucks ou des variables de condition.
[^] # Re: Une solution
Posté par benoar . Évalué à 2.
J'espère que c'est lisible quand même.
[^] # Re: Une solution
Posté par Achille Fouilleul (site web personnel) . Évalué à 1.
Pour préserver les espaces dans le code, utilise des balises <pre>. Lis la doc ;-)
[^] # Re: Une solution
Posté par benoar . Évalué à 2.
Pourquoi pas. Mais comme je pensais que ton but était de pouvoir faire des appels gtk depuis un autre thread, je ne voyais pas pourquoi tu l'utilises.
Par contre, le idle_add() ne te dispense pas de bien locker les accès/écritures aux données de ton thread, car c'est le thread principal qui exécutera le code à ce moment-là.
C'est une question de préférence personnelle, peut-être, mais la plupart des bibliothèques graphiques n'autorisent pas les appels cross-thread, même avec des verrous.
Oui, effectivement, ce n'est pas forcément préconisé, mais c'est possible avec gtk.
D'ailleurs, que se passe-t-il si une exception est levée entre threads_enter() et threads_leave() ?
Tu mets ton leave() dans le finally:, comme indiqué dans le lien que tu donnes en dessous.
Pour préserver les espaces dans le code, utilise des balises . Lis la doc ;-)
Arf, bien vu … Merci ;-)
# La suite...
Posté par thor_tue . Évalué à 1.
2) Mon premier essai je l'ai fait avec la méthode leave/enter mais la FAQ semblait déconseiller cette méthode :
[http://faq.pygtk.org/index.py?file=faq20.006.htp&req=sho(...)]
Je manque évidemment de recul pour trancher, je mémorise vos deux méthodes pour l'instant.
[^] # Re: La suite...
Posté par benoar . Évalué à 2.
C'est déconseillé _sous Windows_. Sous linux, pas de problème.
Je manque évidemment de recul pour trancher, je mémorise vos deux méthodes pour l'instant.
Ça va dépendre de ce que tu veux faire exactement, mais on n'avait pas beaucoup d'infos ici pour ton projet concret ! À toi d'évaluer ce qui te conviens le mieux.
[^] # Re: La suite...
Posté par stopspam . Évalué à 1.
même problème pour moi : je dois utiliser des Thread dans une appli pyGtk (linux/windows) et j'ai trouvé aucun exemple simple et concret, que j'arrive à comprendre et qui fonctionne.
Je suis ouvert à tout bout de code montrant comment utiliser les Threads et qui soit facilement adaptable.
[^] # Re: La suite...
Posté par benoar . Évalué à 2.
Mais bon, dire « je veux faire des threads » et ne pas trouver d'exemple, c'est normal : c'est tellement vague… Je vous conseillerais de d'abord apprendre la programmation threadée sans GUI, et après passer à PyGTK + threads. Et puis, être un peu plus précis sur ce que vous voulez faire.
Par contre, si vous voulez juste copier/coller un bout de code sans réfléchir parce qu'on vous a demandé d'écrire un bout de code avec thread, c'est normal de rien trouver…
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.