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

Threads & Processus C++ Discussion :

Le pattern consumer et producer.


Sujet :

Threads & Processus C++

  1. #1
    Invité
    Invité(e)
    Par défaut Le pattern consumer et producer.
    Salut, je vais tenter d'expliquer mon problème :

    Je possède deux threads, imaginons que le premier soit le CPU et le second le GPU. (Mais ça pourrait être n'importe quoi)
    Ici dans ce cas le GPU joue le rôle de producer et le CPU de consumer.

    Tout deux partagent un buffer, alors le but ça serait que le gpu mette à jour une information quelconque du buffer à l'aide d'un programme (le fragment shader par exemple mais ça pourrait être n'importe quel programme) de manière thread safe sans faire attendre le CPU.

    Voici un pseudo code que j'ai fait qui utilise boost::lockfree.

    Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    boost::lockfree::queue<int> buffer(128);
    class Producer {
        public :
        void operator()(int v) {
            buffer.push(v);
        }
    };
    Producer p;
    FastDelegate<void> fd(&Producer::operator(), &p, 5);
    buffer.consume_one(fd);
    return 0;

    Alors déjà là j'ai un soucis c'est que je ne peux pas passer un type classe à la queue, sinon ça ne compile pas, il me dit que la classe n'a pas de destructeur trivial, et que je définisse un destructeur ou pas dans la classe ça ne change rien, on dirait que les queues lock free n'acceptent que des variables de type primitif.

    Le problème c'est que je ne peux pas accéder à un élément i du buffer, je suis obligé à chaque fois de push et de pop, donc, je ne sais pas du tout comment faire pour accéder une donnée quelconque de mon buffer et la modifier je pensais faire une classe Task qui contient un std::vector<int> mais comme je ne peux pas utiliser d'autre type que des types primitif..., bref, je ne m'y connais pas beaucoup en programmation multi-threadée et je n'ai pas trouvé beaucoup de doc à se sujet.

    Merci pour votre aide.

  2. #2
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    D'après la doc de la bibliothèque, ca accepte les types tels que
    Requirements:
    T must have a copy constructor
    T must have a trivial assignment operator
    T must have a trivial destructor
    Es-tu dans ce cas là?
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  3. #3
    Invité
    Invité(e)
    Par défaut
    Salut,

    j'ai essayé de les déclarer :

    Code cpp : 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
     
    class Task {
        public :
        Task() {
            buffer = std::vector<int>(128);
        }
        Task(const Task& task) {
            buffer = task.buffer;
        }
        Task& operator= (const Task& task) {
            buffer = task.buffer;
            return *this;
        }
        void operator()(int index, int value) {
            buffer[index] = value;
        }
        ~Task() {
        }
        private :
            std::vector<int> buffer;
    };
    int main()
    {
        boost::lockfree::queue<Task> tasks(128);
        Task t;
        tasks.push(t);
        FastDelegate<void> command(&Task::operator(), &tasks.pop(), 0, 255);
        tasks.consume_one(command);
        return 0;

    Mais toujours la même erreur de compilation. :/

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    ||=== Build: Debug in ODFAEG-DEMO (compiler: GNU GCC Compiler) ===|
    /usr/include/boost/lockfree/queue.hpp|81|error: static_assert failed "(boost::has_trivial_destructor<T>::value)"|
    /usr/include/boost/static_assert.hpp|78|note: expanded from macro 'BOOST_STATIC_ASSERT'|
    /home/laurent/Développement/Projets-c++/ODFAEG-DEMO/main.cpp:32:34: note||in instantiation of template class 'boost::lockfree::queue<Task, boost::parameter::void_, boost::parameter::void_, boost::parameter::void_>' requested here|
    ||=== Build failed: 1 error(s), 1 warning(s) (0 minute(s), 5 second(s)) ===|

  4. #4
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    Laisse le compilateur faire son boulot (génération du constructeur par copie, de l'opérateur d''affection et du destructeur) (il fait la bonne chose ici ), ta classe devrait être comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Task {
        public :
        Task() : buffer(128) { }
        void operator()(int index, int value) {
            buffer[index] = value;
        }
        private :
            std::vector<int> buffer;
    };
    PS : À première vue, j'aime pas du tout l'utilisation de ton operator() (et sa signature).

  5. #5
    Invité
    Invité(e)
    Par défaut
    J'ai changer le code de ma classe mais toujours la même erreur en compilation. :/

    Bref, je ne sais plus trop que faire. :/

    PS : j'ai trouvé la solution, il suffisait d'utiliser un pointeur.

    Code cpp : 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
     
    #include <boost/lockfree/queue.hpp>
    #include <thread>
    class Task {
        public :
        Task();
        void operator()(int index, int value);
        private :
            std::vector<int> buffer;
    };
    Task::Task() : buffer(128) {
     
    }
    void Task::operator()(int index, int value) {
        buffer[index] = value;
    }
    using namespace odfaeg::core;
    using namespace odfaeg::math;
    using namespace odfaeg::physic;
    using namespace odfaeg::graphic;
    using namespace odfaeg::audio;
    int main()
    {
        boost::lockfree::queue<Task*> tasks(128);
        Task t;
        tasks.push(&t);
        FastDelegate<void> fd(&Task::operator(), &t, 0, 255);
        tasks.consume_one(fd);
        return 0;
    }

    Bon maintenant je vais voir avec deux thread ce que ça donne (un qui lis le buffer et un autre qui écrit dans le buffer) pour voir si il n'y a pas de crash.

  6. #6
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    Le truc c'est que le destructeur de std::vecteur n'est pas "trivial".
    Pour être thread-safe, utilise plutôt un mutex ?

  7. #7
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 071
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 071
    Points : 12 116
    Points
    12 116
    Par défaut
    Mais de base, les threads sur le CPU et les threads sur GPU n'ont absolument rien à voir, ils ne partagent même pas la même mémoire et c'est au driver graphique de rapatrier le contenu de la mémoire du GPU dans celle du CPU et vis-vers-ça.

    Donc, c'est quoi le vrai problème ?

  8. #8
    Invité
    Invité(e)
    Par défaut
    Désolé je me suis mal exprimé, je voulais dire plusieurs threads côté gpu : un qui exécute les tâches une par une, et un autre qui récupère les valeurs de retour des commandes envoyée au gpu via le fragment shader.

  9. #9
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 071
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 071
    Points : 12 116
    Points
    12 116
    Par défaut
    Sortie des Shader OpenGL, j'y connais rien en programmation GPU, mais je pense que vous devriez indiquer quel est la bibliothèque de threading GPU vous utilisez pour cadrer la discussion.

  10. #10
    Invité
    Invité(e)
    Par défaut Multi-Threading. (Valeurs non modifiées et mal lue)
    Salut, en fait je possède les classes suivantes :

    Task : effectue une instruction.
    Producer : modifie une donnée contenue dans un vecteur.
    Consumer : copie une donnée dans un conteneur (le résultat d'une instruction si l'instruction renvoie un résultat) pour pouvoir la récupérer ensuite avec un autre thread.
    TaskManager : exécute les instructions dans l'ordre dans lequel on lui fournis.

    Pour des raison de performance je souhaite utiliser des conteneurs lock-free et non pas des mutex, j'ai donc écris le code source suivant :

    Code cpp : 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
     
    #include <boost/lockfree/queue.hpp>
    #include <thread>
    class Task {
        public :
        virtual void operator()() = 0;
        unsigned int taskId;
        static unsigned int nbTasks;
    };
    unsigned int Task::nbTasks = 0;
    class TaskManager {
    public :
        TaskManager() : tasks(100) {}
        void addTask (Task* task) {
            odfaeg::core::FastDelegate<void> command (&Task::operator(), task);
            commands.push_back(command);
            tasks.push(task);
        }
        void run() {
            while (true) {
                if (commands.size() > 0) {
                    tasks.consume_one(commands.back());
                    commands.pop_back();
                }
            }
        }
        static void addValue(int value) {
            std::cout<<"add value : "<<value<<std::endl;
            values.push(value);
        }
        static int getValue() {
            int value = -1;
            if (values.pop(value))
                return value;
            std::cout<<"get value : "<<value<<std::endl;
            return value;
        }
    private :
        boost::lockfree::queue<Task*> tasks;
        std::vector<odfaeg::core::FastDelegate<void>> commands;
        static boost::lockfree::queue<int> values;
    };
    boost::lockfree::queue<int> TaskManager::values (100);
    class Producer : public Task {
    public :
        Producer(std::vector<int>& buffer, int index, int value);
        void operator()();
    private :
        std::vector<int>& buffer;
        int index, value;
    };
    Producer::Producer(std::vector<int>& buffer, int index, int value) :
        buffer(buffer), index(index), value(value) {
        taskId = nbTasks;
        nbTasks++;
    }
    void Producer::operator()() {
        buffer[index] = value;
    }
    class Consumer : public Task {
    public :
        Consumer(std::vector<int>& buffer, int index);
        void operator()();
    private :
        std::vector<int>& buffer;
        int index, value;
    };
    Consumer::Consumer(std::vector<int>& buffer, int index) :
        buffer(buffer), index(index) {
        taskId = nbTasks;
        nbTasks++;
    }
    void Consumer::operator()() {
        value = buffer[index];
        TaskManager::addValue(value);
    }
    using namespace odfaeg::core;
    int main()
    {
        std::vector<int> buffer = {0, 1, 2, 3, 4, 5};
        TaskManager tm;
        Task* t1 = new Producer(buffer, 0, 6);
        Task* t2 = new Consumer(buffer, 0);
        tm.addTask(t1);
        tm.addTask(t2);
        std::thread t(&TaskManager::run, &tm);
     
        int value;
        while (TaskManager::getValue() == -1) {
     
        }
        value = TaskManager::getValue();
        std::cout<<"final value : "<<value<<std::endl;
        t.join();
        delete t1;
        delete t2;
        return 0;
    }

    Malheureusement, cela ne marche pas, la valeur retournée par TaskManager est toujours -1. :/
    Et les valeurs dans mon vecteur ne sont pas modifiée non plus. :/

    PS : je n'utilise pas de librairie de multi-threading côté GPU pour l'instant, j'essaye de faire des tests avec le CPU et la librairie standart du c++14 ainsi que boost.
    Dernière modification par Invité ; 10/03/2015 à 21h16.

  11. #11
    Invité
    Invité(e)
    Par défaut
    Moui en même temps si je passe pas value par référence et si j'exécute tâches dans le mauvais ordre ça ne va pas marcher, maintenant ça marche.

  12. #12
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    (le CPU et le GPU n'ont rien à voir en termes de gestion des threads. Ce code n'a pas de sens sur GPU)

    Tu réalises qu'on ne peut pas tester ton code ? Donc difficile de t'aider. Utiliser un déboguer, fais tourner ton programme au pas à pas.

  13. #13
    Invité
    Invité(e)
    Par défaut
    Ok, je ne sais pas ce qu'il en est de la gestion multi-thread au niveau du GPU, mais je pensais à ceci : plutôt que de faire attendre le CPU que la fonction draw aie fini de s'exécuter (ce qui est lent) je voudrais mettre les buffers à jour à chaque fois que un fragment est modifié et non pas à chaque fois que la fonction draw est appelée, je pense faire un système comme j'ai fais là c'est à dire une liste de tâches et chaque tâche effectue une unité de traitement puis rend la main, et pouvoir répartir ainsi les différentes tâches au niveau du gpu, enfin soit, je ne m'y connais pas du tout en programmation multi-thread au niveau du GPU donc il faudra que je me renseigne.

  14. #14
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    La gestion des threads en interne des GPU (ou en "interne" du CPU) et la gestion des échanges entre le CPU et le CPU sont des problématiques complètement différentes. Dans cas, on est sur de la mémoire partagée (plus ou moins, avec plus ou moins de problèmes de cohérence des caches... osef). Dans l'autre, on est sur des échanges de données entre mémoires non partagée (on est sur du streaming, avec une structure en client-serveur). Bref, rien a voir

    C'est quoi cette fonction draw ? C'est la fonction qui lance le rendu par le GPU et qui est lancé par le CPU ? Dans ce cas, non, ce n'est pas long : pas défaut, les appels aux fonctions du GPU sont asynchrones, donc se terminent tout de suite. Donc le CPU à la temps de faire ce qu'il veut. (ou presque, il ne doit pas toucher aux données qui sont en cours d'envoi au GPU. D'où les techniques comme le double buffering ou autre).

    Et quand à l'idée de mettre les buffers à jour à chaque fois qu'ils sont modifiés, mauvaise idée aussi. L'accès au GPU a un temps de latence important, il est préférable d'envoyer les buffers en une seule fois par update de l'affiche que de les envoyer plusieurs fois.

    Pour ton système de taches pour le GPU, comme je l'ai dit, osef. Le GPU fonctionne déjà en client-serveur, avec du streaming asynchrone. Ton système de tâches ne sert à rien pour le GPU.

    Par contre, tu peux avoir un thread de rendu, qui s'occupe des tâches côté CPU qui sont lourdes (mettre à jour les meshs et textures, recalculer les données, etc) et qui communique avec ton thread principal

  15. #15
    Invité
    Invité(e)
    Par défaut
    En fait je pensais que à chaque fois qu'un fragment shader écrivait un fragment dans une texture de rendu, la texture était mise à jour mais en fait ce n'est pas ce qui se passe, la texture est seulement mise à jour à la fin du draw.

    Donc si j'utilise de l'instanced rendering, je n'ai pas la bonne couleur dans mon fragment shader car je n'ai pas la couleur de l'objet qui a été dessiné précédemment mais toujours la couleur de fond vu que la texture n'est pas remise à jour tant que le draw n'a pas fini de s'exécuté côté GPU.

    Seule solution donc => faire plusieurs appels à draw (un par face), ce qui est coûteux et baisse le FPS, je passe d'un FPS de 30 à un FPS de 15-20 maxi.

    En plus je dessine sur plusieurs texture de rendu, donc imagine. :/

    Je pensais donc modifier le driver pour qu'il remette à jour la texture de rendu à chaque fois qu'un fragment est modifié par le fragment shader, plutôt que d'attendre que le fragment shader aie fini de tout dessiné pour remettre la texture de rendu à jour. (Je ne sais pas si c'est possible de faire ça)

    J'ai entendu parler de Vulkan qui sera plus "multi-threadé" mais il n'y a pas encore de date de sortie.

    Donc il n'est pas vraiment question de faire plus de transfert entre CPU et GPU, tout le problème reste du côté du GPU.

  16. #16
    Invité
    Invité(e)
    Par défaut
    Bon apparemment ce n'est possible que de faire cela à partir de la version 420 de opengl, donc, pas le choix, je dois dessiner les faces une par une si je veux que l'order independant transparency fonctionne.

  17. #17
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    J'ai l'impression que tu copies des tutos, mais sans comprendre ce qui se passe.

    Vu ce que tu fais, tu ne devrais pas avoir 15 FPS. Même pas 30. Tu devrais avoir plusieurs centaines de FPS pour des scènes aussi simples.
    Et créer un driver n'est pas une solution viable. MESA fait cela depuis des années, ils sont nombreux et experts dans ce domaine. Tu n'as aucune chance de réussir a créer un driver pour faire ce que tu veux.
    Surtout que le problème est une mauvaise utilisation d'OpenGL. Il y a des contraintes matériel dans le GPU (architecture des mémoires, ordonnancement des threads, etc) et conceptuel (sur la programmation parallèle) qui font que ce que tu veux faire n'a pas de sens (et est complètement contre performant)

  18. #18
    Invité
    Invité(e)
    Par défaut
    Ma scène n'est pas simple!

    Il y a du bump-mapping, du shadow-mapping et de la refraction-mapping.

    Elle peut paraître simple car ce n'est que de la 3D iso avec affichage de deux lumières..., mmm, ok mais quand tu rajoutes du bump-mapping, du shadow-mapping, etc..., les perfs en prennent un sacré coup.

    Et puis que je dessine n'importe quoi avec ce même algorithme ça ne change pas grand chose niveau des perfs, vu que j'utilise des algorithmes générique pour la 3D et la 2D...

    D'ailleurs je suis curieux de savoir si tu arriverais à faire mieux.

    Bref tu n'as pas l'air de comprendre ce que je raconte.

  19. #19
    Membre éclairé

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    393
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 393
    Points : 685
    Points
    685
    Par défaut
    Reste dans tes certitudes. C'est pourtant pas difficile de trouver des projets sur internet qui utilisent aussi ces techniques (par exemple : http://yannick.gerometta.free.fr/base.php?id=glsldemo, fait par 3 étudiants en 3 mois. le FPS sont plafonne a 60 FPS du fait que cela tourne sur Windows, mais sans la synchro verticale, cela peut tourner plus vite.) Télécharge les démos de NVIDIA et fais les tourner sur ton PC, tu verras que 15 FPS, c'est très peu.

    C'est toi qui vient pour des problèmes de performances (parce que 15 FPS, c'est un problème), pas nous. Si quand on te dit quelque chose, tu ne sais que répondre "vous ne comprenez rien" ou "vous n'y connaissez rien", ça sert a rien de poser des questions

  20. #20
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    @Lolilolight:
    Tu n'as pas l'impression que si tu n'es systématique pas compris, ca pourrait venir de ce que tu n'exposes pas ton problème clairement?
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Réponses: 1
    Dernier message: 26/02/2011, 15h10
  2. Réponses: 4
    Dernier message: 24/02/2009, 12h06
  3. Interfaces, Pattern Observer
    Par IProg dans le forum Langage
    Réponses: 8
    Dernier message: 18/12/2003, 14h11
  4. [Design Patterns] Architecture 3 tiers
    Par HPJ dans le forum Design Patterns
    Réponses: 1
    Dernier message: 29/07/2003, 11h49
  5. [langage] expression reguliere motif répétitif dans 1 pattern
    Par comme de bien entendu dans le forum Langage
    Réponses: 11
    Dernier message: 09/04/2003, 16h14

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