Ceci est un post invité de poulpe posté sous licence creative common 3.0 unported.

Coroutines et Machines à états.

Quel est le rapport entre les coroutines et les machines à état me demanderez-vous (ou pas) ?

Et bieeeeen aucun, si ce n’est que vous pouvez déjà consulter deux articles sur ce site qui vous permettent d’en bricoler en python.

Mais bon, les tournevis Playskool et la colle UHU, pour du vrai bricolage de grand, c’est rigolo mais c’est pas forcement très adapté. Je vais donc vous présenter le Black & Decker de l’exécution concurrente, le Rubson de la State Machine : le module greenlet.

Les greenlets c’est quoi ? C’est simplement un fork de l’interpréteur Stackless sous forme de module pour CPython. Ca permet de profiter de la quasi-totalité de Stackless sans mettre en péril l’environnement d’exécution et/ou le code existant.

Mais comment ça s’utilise alors ?

Tout d’abord :

from greenlet import greenlet from greenlet import greenlet

Le principe de base c’est :

def fun1 ( ) : print 42 g2. switch ( ) print 44 def fun2 ( ) : print 43 g2. switch ( ) print 45 g1 = greenlet ( fun1 ) g2 = greenlet ( fun2 ) g1. switch ( ) def fun1(): print 42 g2.switch() print 44 def fun2(): print 43 g2.switch() print 45 g1 = greenlet(fun1) g2 = greenlet(fun2) g1.switch()

Sortie écran :

42 43 44 42 43 44

Ca à l’air simple comme ça, mais on constate une petite chose : on n’affiche jamais 45. Pourquoi ? Et bien parce que quand une greenlet se termine, on ne retourne pas à l’appelant (puisque ce n’est pas réellement un appel) on retourne à la greenlet parente, qui est ici notre main.

En fait, les greenlets sont organisées en arbre, chaque greenlet possède un unique parent qui est lui est définit au moment de sa création. Les appels à « switch » permettent de sauter d’une greenlet à l’autre mais ils n’affectent pas l’organisation de l’arbre.

Heureusement, cet arbe n’est pas en lecture seule et on peut réorganiser les parents comme on veut (du moment qu’on ne crée pas de cycles) :

g1 = greenlet ( fun1 ) g2 = greenlet ( fun2 ) g1. parent = g2 g1. switch ( ) g1 = greenlet(fun1) g2 = greenlet(fun2) g1.parent = g2 g1.switch()

Sortie écran :

42 43 44 45 42 43 44 45

On est passé de ça :



à ça :



Et dans la vraie vie ?

« Oui parce que là, t’es mignon, mais ton exemple bidon ça me sert pas à grand chose. » Du coup on peut essayer de faire un truc utile et de mettre tout ça en forme pour une faire une super Machine à État Orientée Objet en Wolframite (la fameuse MeooW). Au passage on va utiliser un peu d’héritage sur les greenlets et montrer que la fonction exécutable qu’on leur passe à la création dans le premier exemple est simplement affectée à la méthode run() de la greenlet.

max_iter = 5 class Etat1 ( greenlet ) : def run ( self , iteration ) : while True : print "Etat 1, Iteration : %s" %iteration iteration = etat2. switch ( iteration + 1 ) if iteration > max_iter: return iteration class Etat2 ( greenlet ) : def run ( self , iteration ) : while True : print "Etat 2, Iteration : %s" %iteration iteration = etat1. switch ( iteration + 1 ) if iteration > max_iter: return iteration class EtatFinal ( greenlet ) : def run ( self , iteration ) : print "Monde de merde ! Iteration : %s" %iteration etat1 = Etat1 ( ) etat2 = Etat2 ( ) etat_final = EtatFinal ( ) etat1. parent = etat_final etat2. parent = etat_final etat1. switch ( 2 ) max_iter = 5 class Etat1(greenlet): def run(self, iteration): while True: print "Etat 1, Iteration : %s"%iteration iteration = etat2.switch(iteration + 1) if iteration > max_iter: return iteration class Etat2(greenlet): def run(self, iteration): while True: print "Etat 2, Iteration : %s"%iteration iteration = etat1.switch(iteration + 1) if iteration > max_iter: return iteration class EtatFinal(greenlet): def run(self, iteration): print "Monde de merde ! Iteration : %s"%iteration etat1 = Etat1() etat2 = Etat2() etat_final = EtatFinal() etat1.parent = etat_final etat2.parent = etat_final etat1.switch(2)

Sortie écran :

Etat 1 , Iteration : 2 Etat 2 , Iteration : 3 Etat 1 , Iteration : 4 Etat 2 , Iteration : 5 Monde de merde ! Iteration : 6 Etat 1, Iteration : 2 Etat 2, Iteration : 3 Etat 1, Iteration : 4 Etat 2, Iteration : 5 Monde de merde ! Iteration : 6

On peut voir ici que la méthode switch permet de faire passe-plat pour des variables (de la même manière que yield retourne l’argument qu’on send() à un itérateur).

Plus loin

Les greenlets permettent d’aller beaucoup plus loin que ça, notamment en travaillant sur le dynamisme de l’arbre. C’est très pratique pour faire de la gestion d’évènements. Attention tout de même car ça peut vite devenir un enfer à débugger (pas de stack, pas de chocolat).