poulpe – Sam & Max http://sametmax.com Du code, du cul Wed, 30 Oct 2019 15:34:04 +0000 en-US hourly 1 https://wordpress.org/?v=4.9.7 32490438 Les Greenlets c’est bon, c’est bio, mangez-en ! http://sametmax.com/les-greenlets-cest-bon-cest-bio-mangez-en/ http://sametmax.com/les-greenlets-cest-bon-cest-bio-mangez-en/#comments Fri, 17 Aug 2012 23:42:00 +0000 http://sametmax.com/?p=1750 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

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()

Sortie écran :

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()

Sortie écran :

42
43
44
45

On est passé de ça :
cas1
à ça :
cas2

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)

Sortie écran :

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).

]]>
http://sametmax.com/les-greenlets-cest-bon-cest-bio-mangez-en/feed/ 5 1750
Concurrence sans threads en python http://sametmax.com/concurrence-sans-threads-en-python/ http://sametmax.com/concurrence-sans-threads-en-python/#comments Sat, 28 Jul 2012 00:51:37 +0000 http://sametmax.com/?p=1373 Ceci est un post invité de poulpe posté sous licence creative common 3.0 unported.

Je parie que là, maintenant, vous êtes en train de ne pas vous demander “Comment pourrais-je exécuter des actions concurrente sans utiliser de threads en python ?”. Et c’est bien dommage pour vous car la seule chose que j’ai à vous écrire c’est un début de réponse à cette question.

Pourquoi faire ?

Ouep, les threads c’est pas toujours la joie. Au rayon des inconvénients, on retrouve souvent complexité de conception et de debuggage, librairies externes pas toujours thread safe, dégradation des perfs, aucun contrôle sur la granularité de l’exécution, risques liés aux locks pour toute la partie “Atomicité” etc.
Bon tout n’est quand même pas noir, et dans la majorité des cas, un petit coup de threads sera le plus pratique pour faire ce que vous voulez. Mais si votre besoin est vraiment particulier (ou que vous vous ennuyez beaucoup pendant les vacances) voici une solution assez élégante, qui vous laisse le contrôle absolu (MOUHAHAHA) et qui nécessite souvent peu de modifications de votre code existant.

Comment faire ?

Pour faire ça, on va utiliser les générateurs que vous connaissez bien.
Quoi de mieux qu’un petit exemple pour commencer :

  • Paul veut afficher de façon régulière le mot “Loutre”.
  • Jacques, lui, voudrait afficher de la même façon le mot “Tarentule”.
  • Un mec bizarre que personne ne connait désire écrire “Musaraigne”.

Comme ils sont tous les trois très cons et qu’ils sont incapables de se mettre d’accord pour savoir qui commence, ils décident de faire ça tous en même temps. Problème, ils veulent tous afficher leur mot selon une temporisation bien précise sans se gêner les uns les autres.

Voici donc la fonction que chacun de nos trois zoophiles veut utiliser. Vous remarquerez que le seul truc qu’elle possède de spécial, c’est le petit yield à la fin de chaque itération. C’est moi qui ai décidé de le rajouter arbitrairement à cet endroit (parce que c’est moi le chef). C’est en effet la seule modification à apporter à la fonction pour la rendre “éclatable”.

def afficher_un_truc_regulierement(truc, delai, nombre):
    """Affiche un "truc" tous les "delais" un certain "nombre" de fois"""
    import time
    derniere_occur = time.time()
    num = 0
    while num < nombre:
        maintenant = time.time()
        if maintenant - derniere_occur > delai:
            derniere_occur = maintenant
            print str(num) + " : " + truc
            num += 1
        yield     # Je rajoute mon(mes) yield(s) où je veux.

Le placement du yield est important, tous les traitements entre deux yields seront exécutés de façon atomique. Dans le reste de ce tuto, j’appellerai ce groupe de traitements atomique une granule (j’aime bien le mot).
Dès l’ajout du mot-clé yield dans le corps, notre fonction retourne un générateur au lieu de s’exécuter normalement.

On crée ensuite une liste d’actions à effectuer de façon concurrente. Chaque action est un générateur retourné par l’appel à la fonction.

liste_des_actions = []
#Paul :
liste_des_actions.append(afficher_un_truc_regulierement("Loutre", 4, 4))
#Jacques :
liste_des_actions.append(afficher_un_truc_regulierement("Tarentule", 5, 3))
#Le mec bizarre
liste_des_actions.append(afficher_un_truc_regulierement("Musaraigne", 3, 3))

Voici enfin le mécanisme qui permet d’exécuter tout ce beau bordel. Il est assez générique et le code parle de lui même :)

while True:     # Boucle infinie
    if len(liste_des_actions):     # Si il reste des actions
        #On itère sur une copie de la liste (avec [:])
        #pour pouvoir modifier la liste pendant la boucle
        for action in liste_des_actions[:]:
            try:
                action.next()     # On execute une granule
            except StopIteration:
                #Il n'y a plus de granule dans cette action
                #On enlève donc l'action de la liste
                liste_des_actions.remove(action)
    else:
        #Plus aucune action, on finit la boucle infinie
        break
print "Tout est bien qui finit bien."

Ici, l’exemple est simpliste mais on peut l’adapter à des fonctions beaucoup plus complexes et nombreuses, qui ne se présentent pas forcement sous forme de boucle.

Comment faire mieux ?

Je vous laisse avec une piste d’évolution possible qui est assez amusante à implémenter (on rigole avec ce qu’on peut, hein). On peut facilement imaginer un système de priorité dynamique entre les actions. En effet, ici, on ne yield aucune valeur, mais on peut décider d’utiliser le nombre X yieldé (et donc retourné par action.next()) pour sauter les X prochains appels à cette action, ce qui aura pour effet de réduire la priorité de celle-çi par rapport aux autres.

Voilou, j’espère que vous n’utiliserez jamais ça dans du code collaboratif (ou alors si vous n’aimez pas vos collaborateurs à la limite) mais que le jour où vous aurez ce besoin particulier, vous saurez quoi faire.

]]>
http://sametmax.com/concurrence-sans-threads-en-python/feed/ 10 1373