IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Comportement différent d"une même fonction dans mainloop et dans un thread


Sujet :

Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert
    Avatar de ryan
    Homme Profil pro
    Développeur Web
    Inscrit en
    Juin 2003
    Messages
    956
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juin 2003
    Messages : 956
    Billets dans le blog
    1
    Par défaut Comportement différent d"une même fonction dans mainloop et dans un thread
    Bonsoir!

    J'apprends Python en créant une petite appli qui explore l'ensemble de Mandelbrot. Le coeur de cette appli est la fonction qui dessine l'ensemble ou une partie de l'ensemble. Les calculs peuvent parfois être longs, et je désirais ajouter un bouton "Stop". C'était compliqué d'interrompre les boucles quand la fonction était exécutée dans la main loop, et beaucoup plus facile si la fonction s'exécute dans un thread secondaire.

    J'ai donc placé l'appel de la fonction dans un thread et là, surprise, pour des valeurs très petites (1e-28), j'ai commencé a voir apparaître des distorsions dans l'image. Par contre, la même partie de l'ensemble calculée avec une valeur de 1e-27 ne pose pas de problème.

    Pour résumer: la même fonction marche très bien quand appelée dans la main loop, et débloque quand appelée depuis un thread, avec évidemment les mêmes paramètres dans les deux cas.

    Dans le code suivant, je force le calcul de seulement un pixel sur 4, sinon les temps de calcul sont vraiment longs, mais la distorsion de l'image est bien visible.
    Si vous voulez calculer tous les pixels, il suffit de modifier à deux endroits la variable loop_step et de la mettre à 1.

    J'ai évidemment simplifié le code au maximum pour ne garder que les lignes pertinentes à la reproduction du problème.

    J'ai exploré les pistes auxquelles j'ai pensé (vérifier le garbage collection, le nombre de blocs mémoire alloués,...) mais je n'ai rien trouvé de probant.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
     
    import time
    import decimal
    from tkinter import *
    from decimal import getcontext
    from PIL import Image, ImageDraw, ImageTk
    import threading
    from threading import Event
     
    exit_event = Event()
     
    getcontext().prec = 50
     
    interface_frame_width = 200
    root_horizontal_margin = 0
    root_vertical_margin = 74
     
    colors_blue = ("#000000", "#000102", "#000205", "#000409", "#000511",
                   "#010615", "#010617", "#01081c", "#010a23", "#020c28",
                   "#030e30", "#031037", "#04133f", "#051646", "#07194e",
                   "#081d58", "#081d59", "#091e5b", "#0a1f5d", "#0b1f5f",
                   "#0b1f5f", "#0c2061", "#0d2163", "#0f2267", "#102368",
                   "#11246c", "#12256e", "#132670", "#142772", "#152774",
                   "#162876", "#173078", "#172978", "#182979", "#192a7b",
                   "#1a2b7d", "#1b2c7f", "#1c2d83", "#1d2e85", "#1e2f87",
                   '#1f2f88', '#20308a', '#22318e', '#233290', '#243392',
                   '#243494', '#243594', '#243895', '#243996', '#243c97',
                   '#243d98', '#243e99', '#24419a', '#23429a', '#23459c',
                   '#23479d', '#23499e', '#234a9e', '#234b9f', '#234da0',
                   '#234ea0', '#234fa1', '#2251a1', '#2252a2', '#2253a3',
                   '#2255a3', '#2256a4', '#2257a5', '#2259a5', '#225aa6',
                   '#225ba6', '#225da7', '#215ea8', '#2160a8', '#2161a9',
                   '#2163aa', '#2164ab', '#2166ab', '#2168ac', '#2069ad',
                   '#206bae', '#206cae', '#2070b0', '#2071b1', '#1f73b1',
                   '#1f74b2', '#1f76b3', '#1f78b4', '#1f79b4', '#1f7bb5',
                   '#1e7cb6', '#1e7eb7', '#1e80b8', '#1e83b9', '#1e84ba',
                   '#1e86bb', '#1d88bb', '#1d89bc', '#1d8bbd', '#1d8cbe',
                   '#1d8ebe', '#1d90bf', '#1d91c0', '#1e92c0', '#1f93c0',
                   '#2094c0', '#2196c0', '#2397c0', '#2498c0', '#2599c0',
                   '#269ac1', '#279bc1', '#289dc1', '#299ec1', '#2a9fc1',
                   '#2ca0c1', '#2da1c1', '#2ea2c1', '#2fa4c2', '#30a5c2',
                   '#31a6c2', '#32a7c2', '#34a8c2', '#35a9c2', '#36aac2',
                   '#37acc2', '#38adc3', '#39aec3', '#3aafc3', '#3bb0c3',
                   '#3db1c3', '#3eb3c3', '#3fb4c3', '#40b5c3', '#41b6c3',
                   '#43b7c3', '#45b7c3', '#47b8c3', '#49b9c2', '#4bb9c2',
                   '#4dbac2', '#4fbbc1', '#51bcc1', '#53bcc1', '#55bdc1',
                   '#57bec0', '#59bfc0', '#5bbfc0', '#5dc0bf', '#5fc1bf',
                   '#61c1bf', '#63c2bf', '#64c3be', '#66c4be', '#68c4be',
                   '#6ac5bd', '#6cc6bd', '#6ec6bd', '#70c7bd', '#72c8bc',
                   '#74c9bc', '#76c9bc', '#78cabb', '#7acbbb', '#7cccbb',
                   '#7eccbb', '#80cdba', '#82ceba', '#84cfba', '#87d0ba',
                   '#89d1b9', '#8bd1b9', '#8dd2b9', '#90d3b9', '#92d4b9',
                   '#94d5b8', '#97d6b8', '#99d7b8', '#9bd8b8', '#9dd8b8',
                   '#a0d9b7', '#a2dab7', '#a4dbb7', '#a6dcb7', '#a9ddb6',
                   '#abdeb6', '#addfb6', '#afdfb6', '#b2e0b6', '#b6e2b5',
                   '#b7e2b5', '#b8e3b5', '#b9e3b5', '#bbe4b5', '#bde5b4',
                   '#bfe6b4', '#c1e7b4', '#c4e7b4', '#c6e8b4', '#c7e9b3',
                   '#c9e9b3', '#caeab3', '#cbeab3', '#ccebb3', '#cdebb3',
                   '#cfecb3', '#d0ecb3', '#d1edb3', '#d2edb3', '#d3eeb2',
                   '#d5eeb2', '#d6eeb3', '#d7efb4', '#d8f0b4', '#d9f0b7',
                   '#daf0b6', '#daf0b2', '#dcf1b2', '#ddf1b2', '#def2b2',
                   '#dff2b2', '#e0f3b1', '#e2f3b1', '#e3f4b1', '#e4f4b1',
                   '#e5f5b1', '#e6f5b1', '#e8f6b1', '#e9f6b1', '#eaf7b1',
                   '#ebf7b1', '#ecf7b1', '#edf8b2', '#eef8b3', '#eef8b4',
                   '#eff8b5', '#eff9b7', '#f0f9b8', '#f0f9b9', '#f1f9ba',
                   '#f2f9bc', '#f2fabd', '#f3fabe', '#f3fabf', '#f4fac1')
     
     
    class DrawingProcess(threading.Thread):
     
        def __init__(self):
            threading.Thread.__init__(self)
            self.compteur = 1
            self.go = False
     
        def run(self):
     
            while not exit_event.is_set():
     
                if self.go:
     
                    x_min = decimal.Decimal("-1.4492083625918471911137774497")
                    y_min = decimal.Decimal("-0.0219455526775311555223130111")
                    xy_step = decimal.Decimal(1E-28)
                    color_offset = 300
                    loop_step = 4
     
                    myimage.draw_me(x_min, y_min, xy_step, color_offset, loop_step)
     
                # end if
     
            # end while
     
        # end function run
     
     
    # end class
     
     
    class Configuration:
     
        def __init__(self):
     
            self.rows_number = 0
            self.lines_number = 0
            self.interface_width = 0
     
        def set_dimensions(self, horizontal_margin, vertical_margin, interface):
     
            self.rows_number = root.winfo_screenwidth() - horizontal_margin - interface
            self.lines_number = root.winfo_screenheight() - vertical_margin
            self.interface_width = interface
     
     
    # end classe Configuration
     
     
    class Drawing:
     
        def __init__(self):
     
            self.x_min = 0
            self.y_min = 0
            self.xy_step = 0
            self.color_offset = 0
            self.loop_step = 0
            self.drawing_done = False
     
        def draw_me(self, x_min, y_min, xy_step, color_offset, loop_step):
     
            global image_tk
     
            the_start = time.perf_counter()
            self.drawing_done = False
     
            root.title("Calculs en cours...")
            root.update()
     
            nb_colors = len(colors_blue) - 1
            max_iterations = nb_colors + color_offset
     
            cnv.delete("all")
            draw.rectangle((0, 0, myconfig.rows_number, myconfig.lines_number), fill=(0, 0, 0))
     
            x_pixel = 1
            drawing_process.compteur = 1
     
            while drawing_process.compteur <= myconfig.rows_number:
     
                y_pixel = 1
     
                while y_pixel <= myconfig.lines_number:
     
                    x_value = x_min + x_pixel * xy_step
                    y_value = y_min + y_pixel * xy_step
                    x = decimal.Decimal(0)
                    y = decimal.Decimal(0)
     
                    pixel_color = "white"
     
                    x_carre = 0
                    y_carre = 0
                    iterations = 0
     
                    while x_carre + y_carre <= 4 and iterations <= max_iterations:
     
                        x_temp = x_carre - y_carre + x_value
                        y = 2 * x * y + y_value
                        x = x_temp
     
                        iterations = iterations + 1
     
                        x_carre = x * x
                        y_carre = y * y
     
                    # end while
     
                    array_index = iterations - color_offset - 1
     
                    if array_index < 1:
                        array_index = 1
     
                    pixel_color = colors_blue[array_index]
                    draw.rectangle([x_pixel, y_pixel, x_pixel + loop_step, y_pixel + loop_step], pixel_color)
     
                    y_pixel = y_pixel + loop_step
     
                # end while y_pixel
     
                x_pixel = x_pixel + loop_step
                drawing_process.compteur = drawing_process.compteur + loop_step
     
            # end while x_pixel
     
            image_tk = ImageTk.PhotoImage(pil_image)
     
            cnv.create_image(0, 0, anchor=NW, image=image_tk)
            cnv.update()
     
            root.title("Calculs terminés en " + str(time.perf_counter() - the_start)[:4] + " secondes")
            root.update()
     
            self.drawing_done = True
            drawing_process.go = False
     
        # end function draw_me
     
    # end class Drawing
     
     
    def launch_in_main():
     
        x_min = decimal.Decimal("-1.4492083625918471911137774497")
        y_min = decimal.Decimal("-0.0219455526775311555223130111")
        xy_step = decimal.Decimal(1E-28)
        color_offset = 300
        loop_step = 4
     
        myimage.draw_me(x_min, y_min, xy_step, color_offset, loop_step)
     
    # end function launch_drawing
     
     
    def launch_in_thread():
     
        drawing_process.go = True
     
    # end function launch_drawing
     
     
    root = Tk()
    root.resizable(False, False)
    root.title("")
     
    geometry_string = str(root.winfo_screenwidth() - root_horizontal_margin)
    geometry_string = geometry_string + "x"
    geometry_string = geometry_string + str(root.winfo_screenheight() - root_vertical_margin)
    geometry_string = geometry_string + "+0+0"
     
    root.geometry(geometry_string)
    root.configure(background="#454545")
     
    myconfig = Configuration()
    myconfig.set_dimensions(root_horizontal_margin, root_vertical_margin, interface_frame_width)
     
    left_frame = Frame(root)
    left_frame.pack(side=LEFT)
     
    right_frame = Frame(root, bg="#454545")
    right_frame.pack(side=TOP, fill='both', expand=True, padx=10)
     
    cnv = Canvas(left_frame, width=myconfig.rows_number, height=myconfig.lines_number, bg='black', highlightthickness=0)
    cnv.pack(side=LEFT)
     
    butn_in_main = Button(right_frame, text="Calculer dans Main", bg="#777777", command=launch_in_main)
    butn_in_main.pack(side=TOP, fill=X, pady=2)
     
    butn_in_thread = Button(right_frame, text="Calculer Dans Thread", bg="#777777", command=launch_in_thread)
    butn_in_thread.pack(side=TOP, fill=X, pady=2)
     
    myimage = Drawing()
    myimage.drawing_done = True
     
    pil_image = Image.new("RGB", (myconfig.rows_number, myconfig.lines_number))
    draw = ImageDraw.Draw(pil_image)
     
    drawing_process = DrawingProcess()
    drawing_process.start()
     
    root.mainloop()
    Les problèmes de mémoire semblent être courants dans les applications qui jouent avec Mandelbrot, j'ai donc cherché des informations au sujet de la façon dont Python allouait la mémoire dans la main loop versus dans un thread, mais je n'ai rien trouvé...

    Merci d'avance pour vos avis éclairés.

  2. #2
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 741
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 741
    Par défaut
    Salut,

    A cause du GIL, déporter les calculs dans un thread ne va pas gagner grand chose.

    Après, même si tkinter est supposé supporter les threads, ce support a suffisamment de défauts, pour qu'en cas de problèmes, on code comme si ce n'était pas supporté en évitant les mises à jour de l'interface graphique depuis les threads.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Membre Expert
    Avatar de ryan
    Homme Profil pro
    Développeur Web
    Inscrit en
    Juin 2003
    Messages
    956
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juin 2003
    Messages : 956
    Billets dans le blog
    1
    Par défaut
    Merci pour votre réponse.

    Citation Envoyé par wiztricks Voir le message
    A cause du GIL, déporter les calculs dans un thread ne va pas gagner grand chose.
    De mon point de vue, si. Déjà les calculs s'exécutent plus vite dans le thread, jusqu'à deux fois plus vite suivant les paramètres fournis à la routine de calcul.
    Sur des images où on parle en minutes pour les temps de calcul, c'est appréciable. L'évolution envisagée pour cette petite appli est de calculer des série d'images pour en faire des animations, donc les gains de temps de calcul sont bons à prendre.

    Ensuite, si on s'en tient au but premier, j'arrive assez facilement à interrompre les boucles avec un clic de bouton dans un thread, ce que je n'arrivais pas à faire dans la main loop où le clic n'était pris en compte que quand la boucle se terminait d'elle-même. Tout ce que j'ai pu lire au sujet d'interrompre une boucle while pointait dans la direction de passer par un thread.


    Citation Envoyé par wiztricks Voir le message
    Après, même si tkinter est supposé supporter les threads, ce support a suffisamment de défauts, pour qu'en cas de problèmes, on code comme si ce n'était pas supporté en évitant les mises à jour de l'interface graphique depuis les threads.
    Le plus gros des mises à jour se fait dans PIL (les draw.rectangle) et tkinter ne fournit que le canvas où l'image est affichée en fin de calcul. Si on ne voulait pas visualiser l'image en fin de calcul, on pourrait la sauver directement en Jpeg et se passer de tkinter. Ou alors le sens de votre réponse est que dès que l'on fait appel au module tkinter dans une appli pour quelque raison que ce soit, il est recommandé de ne pas utiliser de thread?

  4. #4
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 741
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 741
    Par défaut
    Citation Envoyé par ryan Voir le message
    le sens de votre réponse est que dès que l'on fait appel au module tkinter dans une appli pour quelque raison que ce soit, il est recommandé de ne pas utiliser de thread?
    Ce que j'ai écris est qu'en cas de problème lorsqu'on met à jour directement l'interface via un thread est de faire cette mise à jour "indirectement" (ce qui ne veut pas dire "ne pas utiliser des threads mais créer une interface un peu plus conséquente entre les threads et tkinter).


    Citation Envoyé par ryan Voir le message
    Ensuite, si on s'en tient au but premier, j'arrive assez facilement à interrompre les boucles avec un clic de bouton dans un thread, ce que je n'arrivais pas à faire dans la main loop où le clic n'était pris en compte que quand la boucle se terminait d'elle-même.
    Je ne vois pas pourquoi... et je ne vais pas vous coder un petit exemple pour montrer ce qu'il se passe dans ce cas là.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  5. #5
    Membre Expert
    Avatar de ryan
    Homme Profil pro
    Développeur Web
    Inscrit en
    Juin 2003
    Messages
    956
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juin 2003
    Messages : 956
    Billets dans le blog
    1
    Par défaut
    Bonsoir,


    J'ai fini par trouver quelque chose qui pourrait convenir pour sortir de la boucle, à base de timer, de after et de after_cancel. J'ai écrit un bout de code qui reproduit le principe des calculs (3 boucles imbriquées et la possibilité de sortir de la boucle extérieure) et cela semble fonctionner. Reste plus qu'à implémenter cette solution dans la vraie routine de calcul.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
     
    from tkinter import *
     
     
    def do_outer_loop(delay=1):
        global timer
        global counter
        global limit
        counter = counter + 1
        if counter < limit:
            do_inner_loop()
            timer = root.after(delay, do_outer_loop)
        else:
            print("loops are done")
     
     
    def do_inner_loop():
        print("start of inner loop")
        for i in range(1, 600):
            print(i)
            for j in range(1, 250):
                pass
        print("end of inner loop")
        print()
     
     
    def stop_looping():
        global timer
        if timer:
            root.after_cancel(timer)
            timer = None
        print("escaping from loops")
     
     
    global timer
    timer = None
     
    global counter
    counter = 0
     
    global limit
    limit = 1000
     
    root = Tk()
    btn_go = Button(root, text='start looping', command=do_outer_loop)
    btn_go.pack()
    btn_stop = Button(root, text='stop looping', command=stop_looping)
    btn_stop.pack()
     
    root.mainloop()
    Je suis un peu triste de laisser tomber le thread et le gain de temps qu'il apportait, donc si quelqu'un a une idée au sujet du problème initial, je reste preneur.
    Et je serais également curieux de savoir pourquoi une même fonction s'exécute plus rapidement dans un thread que dans la main loop.

    Je vais de mon côté continuer à investiguer...

  6. #6
    Membre Expert
    Avatar de ryan
    Homme Profil pro
    Développeur Web
    Inscrit en
    Juin 2003
    Messages
    956
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juin 2003
    Messages : 956
    Billets dans le blog
    1
    Par défaut
    Eureka! comme disait l'autre...

    A force de retourner le problème dans tous les sens, j'ai fini par trouver sa cause et donc sa solution: getcontext().prec = 50
    Cette instruction, présente dans main loop, s'assure que les calculs aient la précision nécessaire pour manipuler de tout petits nombres (dans ce cas, 1e-28)

    Or, il apparaît que cette instruction n'opère que dans la main loop et qu'il soit nécessaire de l'inclure dans la méthode run du thread pour que les calculs réalisés dans le thread aient la précision suffisante. Je ne sais pas si c'est un bug ou une fonctionnalité, mais en tout cas, la logique m'échappe.

    Bref, problème résolu, et bien que j'aie aussi trouvé le moyen d'interrompre une boucle sans passer par un thread, je vais le garder pour le gain de vitesse qu'il apporte dans les calculs (quasiment 2 fois plus rapides)

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Utiliser une même fonction dans plusieurs fonctions
    Par EE dans le forum Général JavaScript
    Réponses: 9
    Dernier message: 05/11/2018, 19h47
  2. [Débutant] Comment faire appel à une même fonction dans plusieurs page web ?
    Par Mikounours dans le forum ASP.NET
    Réponses: 6
    Dernier message: 05/03/2013, 15h52
  3. Utilisation d'une même variable dans différents fonctions de Callback
    Par houjuventini dans le forum Interfaces Graphiques
    Réponses: 1
    Dernier message: 16/11/2007, 08h58
  4. Réponses: 5
    Dernier message: 25/05/2007, 17h53
  5. Réponses: 10
    Dernier message: 08/02/2007, 13h18

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo