P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ IF YOU KNEW WHAT YOU WERE DOING YOU'D PROBABLY BE BORED Articles | Connexion
 
~Programmation des threads en Python

Précédent  
  Python  
  Suivant
 Présentation

L'utilisation des threads se révèle d'une importance capitale lors du développement d'applications graphiques. Ceux-ci permettent d'apporter à l'utiliser un grand confort d'utilisation en autorisant l'exécution parallèle de plusieurs tâches.
 Sommaire


 Introduction

Les threads sont des tâches ou processus légers qui permettent au système d'exploitation de la machine de réaliser plusieurs opérations en même temps. En réalité, cette simultanéité ne se révèle qu'illusoire d'un point de vue technique. Néanmoins, l'utilisateur percevra les choses de la sorte. Bien entendu leur utilisation ne se cantonne en aucun cas aux seuls logiciels pourvus d'interfaces graphiques. Toutefois, il s'agit de leur environnement d'application le plus répandu à l'heure actuelle.

Le principe s'avère simple : quand l'utilisateur lance une tâche qui demandera un certain temps d'exécution (au-delà de plusieurs secondes ou dizaines de secondes) il paraît impensable de suspendre la réactivité de l'interface graphique. C'est pour cela par exemple que l'on peut continuer de manipuler les menus d'un outil de retouche d'image durant l'application d'un filtre d'effet spécial. De la même manière, un client de courrier électronique pourra contrôler l'arrivée de nouveaux messages toutes les 10 minutes en exécutant un thread. qui ne fera rien pendant tout ce temps, puis se connectera au serveur POP. Et pourtant le reste de l'outil reste parfaitement utilisable.

Nous allons donc nous pencher sur le module Python permettant la création et la manipulation de threads avant de résoudre un intéressant problème.


 Le module threading

Python propose au sein de sa bibliothèque standard deux modules dédiés à la gestion de ces fameux threads : thread et threading. Nous ne nous intéresserons qu'au second qui n'est autre qu'une sur-couche du premier. Le module threading donc, contient diverses classes permettant de manipuler les threads avec finesse. Il offre les outils nécessaires pour créer des verrous, des sémaphores et ainsi de suite.

La classe la plus importante reste cependant celle, curieusement, intitulée Thread. Nous disposons dès lors de deux manières pour réaliser une exécution parallèle : hériter de celle dernière ou lui fournir un objet exécutable. Si vous optez pour la première méthode, il vous suffira de surcharger la méthode run(). Le listing 1 présente un petit exemple d'exécution entrelacée de deux threads affichant des nombres dans la console. Le premier, t1, affiche un nombre entre 0 et 9 avant de faire une pause de 300 milli-secondes tandis que le second, t2, n'attend que 100 milli-secondes entre deux affichages. En observant le produit de l'exécution de ce script dans le shell, vous constaterez que les deux processus légers fonctionnent bien en parallèle.

Il se peut toutefois que vous ne souhaitiez pas, pour une raison ou pour une autre, hériter de la classe Thread. Dans une telle situation, il vous suffit de fournir au constructeur, dont la définition exacte suit, de celle-ci une méthode qui se substituera à run() :

Thread(group = None, target = None, name = None, args = (), kwargs = {})
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Le premier paramètre reste pour le moment réservé à un usage futur. Le deuxième permet de spécifier la méthode à exécuter et les suivants respectivement le nom du thread, les arguments à fournir à la cible et enfin des arguments nommés pour cette même cible. Le listing 2 contient le code source permettant de réaliser le même programme que dans le listing 1, suivant notre nouvelle méthode.


 Deadlock

Bien que leur utilisation se révèle aisée, les threads peuvent provoquer des bugs vicieux particulièrement ardus à corriger. Vous trouverez sur le CD-Rom accompagnant ce magazine une application écrit en Python et PyQT, nommée ProgXRC. Cette dernière représente un client IRC relativement complet qui fait appel en plusieurs endroits aux threads. Ainsi, le moteur, présent dans le fichier IrcServerConnection.py, de gestion du protocole IRC tourne au sein de son propre processus léger. Mais il faut savoir que tout programme Python s'exécute dans un thread principal judicieusement appelé MainThread. Et c'est ici que les ennuis commencent.

Imaginez que vous souhaitiez simplement vous connecter à un serveur IRC pour aller dialoguer avec quelque amateur du canal #pcteam. Pour ce faire, vous cliquez donc benoîtement sur le bouton de connexion dans l'interface graphique. Et les ennuis commencent, l'application ne répond plus. Lors d'une connexion à un serveur IRC, de nombreux messages de bienvenue sont émis par le serveur. Le moteur va donc recevoir ces derniers et tenter de les afficher dans une zone de texte de la fenêtre du programme. Rappelez-vous à présent que nos deux threads, celui du moteur et le MainThread, ne s'exécutent au niveau de la machine non pas en parallèle mais l'un après l'autre, très rapidement.

Ainsi quand le moteur contient une boucle infinie attendant la réception de messages en provenance du serveur. Quand il en reçoit un, il va appeler une méthode de l'interface graphique, que nous appellerons displayMessage(). Cette dernière appartenant à un autre thread, son exécution ne sera possible que lorsque le moteur aura rendu la main en terminant son action en cours. Malheureusement, pour terminer cette action, qui correspond à l'invocation de displayMessage(), il lui faut rendre la main. En clair, le moteur attend MainThread qui attend le moteur qui. et ainsi de suite. Ce type de problème, relativement courant quand on programme avec des threads, se trouve connu sous le terme de "deadlock".

Pour y remédier, le logiciel ProgXRC apporte une solution élégante. Chaque message provenant du serveur se voit stockée à son arrivée dans une queue (que Python propose par l'entremise du module Queue) partagée par le moteur et l'interface graphique. En outre nous créons une instance de QTimer, possédant son propre thread, qui déclenche un signal intitulé timeout() toutes les 100 milli-secondes. A chaque fois que ce signal sera déclenché, la méthode processIncoming() de l'interface sera exécutée. Celle-ci prendra le premier message de la queue et effectuera son traitement. En conséquence, toutes les actions nécessitant d'invoquer des méthodes de l'interface graphiques seront invoquées dans MainThread et le deadlock sera évité. Le listing 3 présente la gestion de la queue dans IrcServerConnection tandis que le listing 4 présente le traitement d'une tâche de la queue.

Ne vous fiez donc pas à la simplicité apparente d'emploi de ces processus légers et considérez toujours le cas probable d'un deadlock quand votre application se fige. Il vous sera bien souvent difficile de vous sortir de telles situations.

from threading import Thread
import time

class ThreaddyKruger(Thread):
  def __init__(self, sleepDelay):
    Thread.__init__(self)
    self.sleepDelay = sleepDelay

  def run(self):
    for i in range(10):
      print i
      time.sleep(self.sleepDelay)

t1 = ThreaddyKruger(.3)
t2 = ThreaddyKruger(.1)
t1.start()
t2.start()
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
from threading import Thread
import time

def runner(args):
  for i in range(10):
    print i
    time.sleep(args)

t1 = Thread(target = runner, args = (.3, ))
t2 = Thread(target = runner, args = (.1, ))
t1.start()
t2.start()
       
      
JextCopier dans Jext
self.emetteur = QObject()
self.queue = Queue.Queue()

self.gui = laFenetrePrincipale
self.gui.queue = self.queue

self.timer = QTimer()
self.emetteur.connect(self.timer, SIGNAL("timeout()"), self.gui.processIncoming)
self.timer.start(100)
# ajout d'une tâche dans la queue
self.queue.put(("MESSAGE", nick, source, text))
       
      
JextCopier dans Jext
def processIncoming(self):
  while self.queue.qsize():
    try:
      args = self.queue.get(0)
      msgType = args[0]

      if msgType = "MESSAGE":
        displayMessage(args)

    except Queue.Empty:
      pass
       
      
JextCopier dans Jext


par Romain Guy
romain.guy@jext.org
http://www.jext.org
Dernière mise à jour : 14/10/2006


Précédent  
  Python  
  Suivant

 
#ProgX©2005 Mathieu GINOD - Romain GUY - Erik LOUISE