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

C++ Discussion :

programmation multithread en C++


Sujet :

C++

  1. #1
    Membre émérite
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    1 537
    Détails du profil
    Informations personnelles :
    Localisation : Canada

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 537
    Points : 2 548
    Points
    2 548
    Par défaut programmation multithread en C++
    Bonjour,

    Je m'interesse a la programmation multithread, mais j'ai du mal a trouver de la bonne documentation sur celle-ci.

    Par exemple :
    1/ Ou trouver un information qui me dise si tel ou tel element des bibliotheques Boost ou bien STL sont "thread-resistant".
    2/ Comment dejouer les optimisation faites par le compilos, pour faire des double checks par exemple, pour instancier des singletons.
    3/ est-ce a moi ou au processeur, au systeme ou au processeur de gerer les problemes de coherence de cache ?
    4/ et si c'est a oi, comment faire ? je n'ai trouvé aucun doc la dessus.

    Enfin bref, mon probleme n'est pas le fonctionnement de bibliotheques comme pthread ou son portage C++ dans boost, mais plutot sur le bon usage de celle-ci.

  2. #2
    screetch
    Invité(e)
    Par défaut
    aie aie aie ma que difficile!!

    le multithread on ne trouve pas beaucoup de doc dessus car c'est complique.

    1) Les pages de manuel des fonctions standards peuvent te dire lesquelels sont REENTRANTES ce qui est un terme suffisemment fort pour les rendre thread safe si tout va bien, mais seulement dans certaines conditions.

    Exemple :
    la methode char *strtok(char* string, char* sep);
    cette fonction renvoie le prochain token dans "string" du separateur. Si je dis pas de betise ca s'utilise comme suit :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    	char teststr[] = "a b c d e f g h i";
    	char* token = strtok(teststr, " ");
    	do
    	{
    		printf("%s\n", token);
    	}
    	while(token = strtok(0, " "));
    	return 0;
    ce qui se passe ici c'est que la fonction strtok se souvient de la chaine passee en parametre la premiere fois, et elle va partir du principe que les fois d'apres tu veux la suite. Elle stocke donc un etat

    cette fonction est marquee comme non reentrante; si tu l'appelles de deux contextes different, un des deux contextes va imanquablement faire foirer l'autre.

    Il existe une fonction reentrante qui fait la meme chose; cette fonction prend un parametre supplementaire qui est modifie par la fonction et qu'on lui redonne a chaque fois. cela lui evite de bidouiller un truc en interne, et maintenant on peut appeler la fonction de deux contextes differents, le resultat sera probant.

    MAIS meme cette definition compliquee ne peret pas de dire que la fonction est thread safe! elle ne l'est pas (meme reentrante), elle ne fait que fournir des garanties mineures de fonctionnalité.

    Si tu appelles la fonction depuis deux threads differend en lui donnant des donnees differentes, alors le comportement sera correct. En revanche si tu l'appelles avec les memes donnees, alors le resultat est imprevisible; certaines fois ca marchera et d'autres fois les fonctions vont se marcher sur les pieds car ils ecrivent au meme endroit.



    Un autre exemple : la fonction printf est reentrante mais en effectuant printf("plouf\n"); et printf("plop\n"); en meme temps, tu vas aboutir a un affichage etrange
    plooup
    f
    ou un autre truc zarbi car la fonction n'est pas thread safe.

    il est deja difficile d'obtenir la reentrance d'une fonction, alors pour la thread safety c'est quasiment impossible. Au lieu de fournir des versions thread safe, les developpeurs fournissent en general des routines reentrantes (la plupart du temps) mais que le programmeur doit rendre thread safe.

    La reentrance (seule notion que les bibliotheques fournissent en general) est seulement la garantie que la meme fonction appliquee sur deux donnees DIFFERENTES peut etre realisee en parallele.



    Pour en revenir a la thread safety, pour la fonction (deja reentrante) strtok_r, il faudrait pouvoir "locker" la chaine que l'on modifie. Si strtok_r veut y acceder il doit la reserver. Ainsi si deux strtok_r veulent bidouiller "a b c d e f g h i" alors un des deux doit attendre que l'autre aie fini.

    Je ne sais pas si je me fais bien comprendre, mais pour resumer : la fonction strtok n'est pas reentrante et il ne faut jamais l'utiliser en contexte multithread.
    la fonction strtok_r est reentrante mais l'appliquer dans deux threads separes sur la meme donnee va donner un resultat erroné

    Il existe des bibliotheques fournissant des composants thread-safe (exemple : Qt). Cela est precise dans la doc. Les composants STL et boost ne sont jamais thread safe!






    2) Je ne sais pas ce que tu essayes de faire, je suppute le genre de code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    Singleton* Singleton::get()
    {
        if(! instance)
        {
            LOCK;
            if(! instance)
            {
                instance = new Singleton();
            }
            UNLOCK
        }
        return instance;
    }

    ce code a deja ete vu des dizaines de millions de fois, et il n'est PAS THREAD SAFE!! en debug sans optis, comme en release. (Ca doit etre ca qu'on appelle le double check non?)
    des tas de gens se sont arraches les cheveux et tous ont aboutis a une seule conclusion : la seule facon de faire un Singleton thread safe c'est :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    Singleton* Singleton::get()
    {
        LOCK;
        if(! instance)
        {
            instance = new Singleton();
        }
        UNLOCK
        return instance;
    }

    et crois moi des gens tres intelligents ont essaye mieux

    3) et 4) pour le cache c'est un autre truc de ouf!
    il faut que tu declares tes donnees partagees volatile.
    Cela signifie que le compilateur ne mettra pas le resultat dans un registre, il ira verifier la memoire. Toute donnees accedee par deux threads doit etre declaree volatile. Verifie le mot cle volatile pour plus d'info.

    Et meme la ca suffit pas! Dans le cas special ou tu essayes de te servir de la memoire pour synchroniser des threads c'est mal barré aussi :-/

    exemple tres simple :
    Thread 1
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
     
    extern volatile int data;
    extern volatile bool ready;
     
    void send()
    {
        data = 12;
        ready = true;
    }
    Thread 2
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    extern volatile int data;
    extern volatile bool ready;
     
    void receive()
    {
        while(! ready) /*wait*/;
        /* la donnee est prete */
        use(data);
    }

    malheureusement ce cas la n'est pas thread safe non plus (puree c'est casse pieds le multi threading)

    la raison c'est que rien ne force le controleur memoire a tout ecrire dans l'ordre; celui ci place les requetes en queue et les ecrit quand bon lu isemble, comme ca lui plait.
    Il peut donc decider d'ecrire "ready" avant "data", et la PAF ton thread 2 verifie ready, youhouh ca marche, tu lis data et... c'est pas (encore) la bonne valeur. Le controleur memoire a decide de ne pas encore ecrire data. Et si ca lui chante tu ne peux rien y faire.

    Il existe pour contrer ce probleme un mecanisme appele "memory barrier" (a lire sur wikipedia) qui force l'ordonnancement des stockages ou des lectures.

    Dans le cas plus haut il faudrait placer une "write barrier" : cela signifie que l'ordonnancement devient important : avec la write barrier tu demande a ce que TOUT CE QUI SE TROUVE AVANT la barriere soit effectivement ecrit AVANT TOUT CE QU'IL Y A APRES

    exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    void send()
    {
        data = 12;
        WRITE_BARRIER;
        // je veux que tous les trucs avant la barrier (soit data) soit ecrits avant ce qui suit (soit ready)
        ready = true;
    }








    Comme tu peux le voir rien qu'en 4 questions on a balaye la reentrance, le bug du singleton (a propos si tu vois pas pourquoi ce n'est pas thread safe je peux l'expliquer; mais si tu ne le vois pas j'ai peur que tu loupes des problemes de thread safety du meme niveau, ca fait donc un bon exercice) et les memory barrier pour le controleur memoire.

    Je ne pense pas que tu avais entrevu 10% de ces problemes, c'est pour ca que la doc sur les threads est dure a trouver : il est deja difficile de connaitre les problemes de multithreading, c'est donc dur de trouver les solutions.

    Bon mal de crane cher codeur ^^

  3. #3
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par deadalnix Voir le message
    1/ Ou trouver un information qui me dise si tel ou tel element des bibliotheques Boost ou bien STL sont "thread-resistant".
    Pour la S(T)L, nulle part, comme rien n'est spécifié dans le standard, qui ignore actuellement la notion de multithread, chaque implémentation est libre de faire ce qu'elle veut. Par exemple, l'implémentation que j'utilise en général document ça http://msdn.microsoft.com/library/de...rdCLibrary.asp

    En absence de doc, si on se sent en veine, et si on ne fait pas un système mettant en danger la vie des gens, on peut supposer en général un mode "sans surprises", c'est à dire que si on accède à chaque objet que dans un seul thread à la fois, il n'y a pas de données partagées non documentées qui peuvent faire tout foirer.

    Citation Envoyé par deadalnix Voir le message
    2/ Comment dejouer les optimisation faites par le compilos, pour faire des double checks par exemple, pour instancier des singletons.
    Normalement, tu utilises des primitives spécifique à du multithread (locks, compare&swap...), et dans ce cas un compilo intelligent les connait, et sait qu'il ne doit pas bouger le code autour, et un compilo bête ne les connait pas, et comme pour toute fonction inconnue sait qu'il ne doit pas bouger le code autour. Donc ça marche bien.
    Si tu n'utilises aucune de ces primitives, je ne sais pas trop ce que tu espères atteindre comme sécurité multithread, mais j'ai bien peur que ça ne marche pas, même sans réorganisation du code par le compilo (en particulier, si le double-check est bien ce à quoi je pense, ça ne fonctionne pas...).
    Citation Envoyé par deadalnix Voir le message
    3/ est-ce a moi ou au processeur, au systeme ou au processeur de gerer les problemes de coherence de cache ?
    Je ne sais pas à qui c'est, mais ce n'est pas à toi en tout cas.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  4. #4
    Expert éminent

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Points : 6 911
    Points
    6 911
    Par défaut
    Citation Envoyé par deadalnix Voir le message
    1/ Ou trouver un information qui me dise si tel ou tel element des bibliotheques Boost ou bien STL sont "thread-resistant".
    On dit "thread-save". C'est dans la doc de chaque bibliothèque. Si c'est pas documenté, considère que ce ne l'est pas. Et n'oublie pas qu'il y a plusieurs sorte de thread savety.

    2/ Comment dejouer les optimisation faites par le compilos, pour faire des double checks par exemple, pour instancier des singletons.
    Ne cherches pas à déjouer cela, les processeurs font les mêmes... Le problème dans le cas du double check est un problème de cohérence de cache, donc:
    3/ est-ce a moi ou au processeur, au systeme ou au processeur de gerer les problemes de coherence de cache ?
    La responsabilité est à plusieurs niveaux: les processeurs offrent certaines garanties.

    Les systèmes batits au dessus s'en servent pour en fournir d'autres -- parfois en ne permettant pas ce que le processeur autorise pourtant, parfois en donnant des garanties plus fortes.

    Les OS en fournissent encore d'autres -- en général ils se contentent de choisir un de ceux possibles pour le système et configurent le système de manière adéquate.

    Et de toute manière ton code doit utiliser les primitives du processeur ou du systèmes pour en profiter.

    Ne pas oublier que ce n'est pas parce que la version courante n'utilise pas une liberté que ce ne sera pas le cas dans le futur.

    Résumé: utilise les primitives de synchro de l'OS ou des bibliothèques baties par des spécialistes. Hors de cela, peu de salut... tu peux devenir spécialiste, mais il faut des capacités d'investigations supérieures aux tiennes si

    4/ et si c'est a oi, comment faire ? je n'ai trouvé aucun doc la dessus.
    La doc des processeurs est en général assez complète. Si j'ai bonne mémoire, celle du Sparc est presque un tutorial en la matière.

    Les documents de POSIX (j'ai pas l'URL sous la main, ils sont disponibles gratuitement à l'opengroup) et de Windows (Herb Sutter les a communiqué au comité C++, "ISO C++" et tu as de la chance auprès de google) décrivent ce qui est garanti par ces OS. Dans les documents du comité, tu vas trouver aussi des propositions de ce qui sera exigé par la future norme C++.
    Les MP ne sont pas là pour les questions techniques, les forums sont là pour ça.

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    780
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Mai 2006
    Messages : 780
    Points : 1 176
    Points
    1 176
    Par défaut
    le mot clé volatile ne peux pas être utilisé sur les objets de la SL C++ ( Screech parle beaucoup de C en fait mais moins de C++ ).

    La SL ne connait pas les threads mais normalement dans son implémentation comme le dit JolyLoic il n'y a pas de "surprises": pas de fonctions du style de strtok en C.

    J'ai trouvé boost.thread bien sympa à utiliser avec son ensemble de lock/mutex/condition etc.. Je n'ai jamais eu de problème pour l'instant avec les objets de la SL ou boost.

  6. #6
    Membre expérimenté

    Profil pro
    Inscrit en
    Juin 2006
    Messages
    1 294
    Détails du profil
    Informations personnelles :
    Localisation : Royaume-Uni

    Informations forums :
    Inscription : Juin 2006
    Messages : 1 294
    Points : 1 543
    Points
    1 543
    Par défaut
    Salut,

    Citation Envoyé par Jean-Marc.Bourguet Voir le message
    On dit "thread-save". (...) Et n'oublie pas qu'il y a plusieurs sorte de thread savety.
    C'est pas plutôt thread-safe et thread-safety ?

    MAT.

  7. #7
    screetch
    Invité(e)
    Par défaut
    ne disons pas de betises SVP ^^

    je ne parle pas de la STL car cela pose plus de probleme de parler de volatile std::vector<int> que de volatile bool


    pourtant les memes problemes existent, avec le defaut d'etre difficilement solubles

    voici un exemple de code :
    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
    #include <vector>
    #include <windows.h>
     
     
    int thread1fun(std::vector<int>* vec)
    {
        vec->push_back(0);
        printf("initial value : %d\n", vec->at(0));
        vec->pop_back();
        while(vec->empty()) /*wait*/;
        printf("received an element : %d\n", vec->at(0));
        return 0;
    }
     
    int thread2fun(std::vector<int>* vec)
    {
        Sleep(1000);
        printf("pushing an element\n");
        vec->push_back(1);
        return 0;
    }
     
     
    int main(int argc, char *argv[])
    {
        std::vector<int> s_ListOfInt;
        HANDLE thread1 = CreateThread( 0, 12000, (LPTHREAD_START_ROUTINE)thread1fun, &s_ListOfInt, 0, 0);
        if(!thread1)
            return 1;
        HANDLE thread2 = CreateThread( 0, 12000, (LPTHREAD_START_ROUTINE)thread2fun, &s_ListOfInt, 0, 0);
        if(! thread2)
            return 1;
        WaitForSingleObject(thread1,2000);
        WaitForSingleObject(thread2,2000);
        return 0;
    }
    ici, sous Visual il n'y a pas de probleme. Compilé avec GCC en release, ce code va provoquer une boucle infinie car le vecteur va travailler entierement sur des registres et donc il ne verra pas la difference apportee a la memoire par l'autre thread.

    Il est impossible de declarer std:.vector<> volatile car alors seules les methodes volatiles pourraient etre appelees (et elles sont pas legion : il y en a pas)

    la solution : ben y'en a pas dans la STL.

    voici un code avec une classe MiniVector qui corrige le probleme :
    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
    #include <windows.h>
    #include <cstdio>
     
    template< typename T >
    class MiniVector
    {
    private:
        size_t m_size;
        T       m_value[12];
    public:
        MiniVector() :
            m_size(0),
            m_value()
        {
        }
     
        void push_back(const T& value)
        {
            m_value[m_size++] = value;
        }
     
        void pop_back()
        {
            m_size--;
        }
     
        const T& at(size_t index) const
        {
            return m_value[index];
        }
     
        bool empty() const
        {
            return m_size == 0;
        }
     
        void push_back(const T& value) volatile
        {
            m_value[m_size++] = value;
        }
     
        void pop_back() volatile
        {
            m_size--;
        }
     
        const volatile T& at(size_t index) const volatile
        {
            return m_value[index];
        }
     
        bool empty() const volatile
        {
            return m_size == 0;
        }
    };
     
     
    int thread1fun(volatile MiniVector<int>* vec)
    {
        vec->push_back(0);
        printf("initial value : %d\n", vec->at(0));
        vec->pop_back();
        while(vec->empty()) /*wait*/;
        printf("received an element : %d\n", vec->at(0));
        return 0;
    }
     
    int thread2fun(MiniVector<int>* vec)
    {
        Sleep(1000);
        printf("pushing an element\n");
        vec->push_back(1);
        return 0;
    }
     
     
    int main(int argc, char *argv[])
    {
        MiniVector<int> s_ListOfInt;
        HANDLE thread1 = CreateThread( 0, 12000, (LPTHREAD_START_ROUTINE)thread1fun, &s_ListOfInt, 0, 0);
        if(!thread1)
            return 1;
        HANDLE thread2 = CreateThread( 0, 12000, (LPTHREAD_START_ROUTINE)thread2fun, &s_ListOfInt, 0, 0);
        if(! thread2)
            return 1;
        WaitForSingleObject(thread1,2000);
        WaitForSingleObject(thread2,2000);
        return 0;
    }
    ici, on voit que l'on a du dupliquer toutes les methodes pour mettre le mot cle volatile. Aie . Ensuite, on peut declarer un MiniVector<T> volatile et appeler les methodes quand meme (ce que la STL ne permet pas). Le compilateur choisira la meilleure fonction entre la volatile et la pas volatile, selon le type d'entree (vector volatile ou pas)


    A noter que les versions non volatile des methodes sont superflues car cela fonctionne tres bien sans; mais il y a peu de chance que vous souhaitiez appeler uniquement des methodes volatiles ce qui force une baisse des performances, du coup avoir un overload volatile ou non-volatile permet d'obtenir le meilleur des mondes.
    On attend evidemment une version du C++ qui permette de ne pas copier coller les methodes pour rendre la classe compatible volatile.




    Comme quoi les memes problemes en C++ existent toujours, ils ont juste ete encapsules mais je trouvais ca plus clair de ne pas les mentionner

  8. #8
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Je serais fort surpris qu'il y ait un impact de perf sur les fonctions volatiles. volatile sur les objets agit comme const : un marqueur.
    C'est d'ailleurs un moyen pour définir du code résistant aux races conditions ; cf l'article d'Andrei Alexandrescu à ce sujet.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  9. #9
    screetch
    Invité(e)
    Par défaut
    il y a un impact enorme, celui de mettre THIS en volatile comme les methodes const passent THIS en const. Si toute tes methodes sont olatiles alors a chaque fois que tu accedes a un champ de la classe tu dois aller le lire en memoire.

  10. #10
    Expert éminent

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Points : 6 911
    Points
    6 911
    Par défaut
    Citation Envoyé par Mat007 Voir le message
    Salut,



    C'est pas plutôt thread-safe et thread-safety ?

    MAT.
    Les MP ne sont pas là pour les questions techniques, les forums sont là pour ça.

  11. #11
    Membre éprouvé
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    780
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Mai 2006
    Messages : 780
    Points : 1 176
    Points
    1 176
    Par défaut
    je ferais plus un truc du style en utilisant un vector normal plutôt que redéfinir un nouveau vector, enfin ça dépend du contexte

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
           boost::mutex::scoped_lock lock( vectorEmptyMutex );
     
            while( vector.empty() )
            {       
                vectorEmptyCondition.wait( lock );
            }
    avec vectorEmptyCondition étant un boost::condition

  12. #12
    screetch
    Invité(e)
    Par défaut
    Ca ne resout pas le probleme volatile.

    Le fait de locker et le fait de rester dans le registre sont deux problemes different.

    Ici tu peux avoir une boucle infinie quand meme car la fonction empty est inline et travaille sur le registre, lequel ne change jamais (puisque seule la memoire change). L'ajout de volatile forcera le compilateur a generer du code qui va relire la memoire et pas le registre, et cela fonctionnera.

    Les mutex ne resoudront jamais ce probleme.

  13. #13
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Points : 4 625
    Points
    4 625
    Par défaut
    Visual C++ a une extension non standard au mot-clé volatile: la lecture produit une acquire barrier et l'écriture une release barrier.
    Le prochain standard, qui contient les outils pour gérer ces barrières, a néanmoins décidé de ne pas les implémenter de cette manière, car ce n'est pas du tout là qu'est l'utilité originelle de volatile.

    De toutes façons, utiliser volatile, même sous Visual C++, ne résout aucunement les problématiques réelles liées au multithreading.
    Ça n'aide que dans les rares cas où t'as du pot et que ça permet de faire des lectures/écritures atomiques.
    Boost ftw

  14. #14
    Membre émérite
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    1 537
    Détails du profil
    Informations personnelles :
    Localisation : Canada

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 537
    Points : 2 548
    Points
    2 548
    Par défaut
    Merci pour vos reponses. J'avais en effet cerné ce genre de problemes, comme le fait qu'un mutex ne garantisse rien si les données sont stockées dans des registres.

    Et si on veux une application performante, impossible de tout mettre en volatile . . .

    Il n'existe vraiment aucune doc la dessus ? (sentant que j'ai bien lu que la doc des sparc etait une reference, mais dans la mesure du possible je me limiterais a du x86, sous windows et linux).

    Pas de doc du genre le multithread pour les nuls par exemple donc . . .

  15. #15
    screetch
    Invité(e)
    Par défaut
    Citation Envoyé par loufoque Voir le message
    Visual C++ a une extension non standard au mot-clé volatile: la lecture produit une acquire barrier et l'écriture une release barrier.
    Le prochain standard, qui contient les outils pour gérer ces barrières, a néanmoins décider de ne pas les implémenter de cette manière, car ce n'est pas du tout là qu'est l'utilité originelle de volatile.

    De toutes façons, utiliser volatile, même sous Visual C++, ne résout aucunement les problématiques réelles liées au multithreading.
    Ça n'aide que dans les rares cas où t'as du pot et que ça permet de faire des lectures/écritures atomiques.
    encore une fois je le redis, le mot cle VOLATILE, les operations ATOMIQUES et les locks sont trois choses distinctes (enfin deux)


    le mot cle VOLATILE redemande la valeur en memoire. Tu as beau mettre tous les mutex que tu veux, si une variable est gardee en registre elle ne bougera jamais (y compris avec des memory barriers ou avec des mutex)

    les operations atomiques permettent de garantir une execution correcte en simultane, c'est uniquement valable pour quelques operations (par exemple une instruction permet de decrementer la valeur et lire le resultat, ce qui est atomique compare a decrementer puis lire la valeur).

    Les locks sont la pour effectuer des operations de plus grosse envergure mais coutent plus cher. Sinon ca marche un peu comme un atomique, tu dis que ton code la devrait etre atomique et d'une certaine facon il le devient.


    Pour de la doc, pas facile, je e connais pas en tous cas

  16. #16
    Membre éprouvé
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    1 064
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2005
    Messages : 1 064
    Points : 1 053
    Points
    1 053
    Par défaut
    En absence de doc on peut faire quelque suppositions.
    Tout d'abord, qu'est-ce qui n'est pas thread safe dans la biblio standard C?
    Les nombres aléatoires et certaines fonctions de manipulation des dates pour ce que je connais.
    En théorie, les classes de bases comme les conteneurs, strings ou flux n'ont pas vraiment de raison d'utiliser ça, à part peut-être la fonction permettant de mélanger les conteneurs (donc exit). Pour les dates rien n'est géré (donc on est peinards).
    Sinon, il y a bien une classe qui nécéssite de stocker des données en static je pense: les locales. Ca pourrait donc poser problème, mais il faudrait se renseigner auprès de quelqu'un qui connait mieux leur fonctionnement.

  17. #17
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Points : 4 625
    Points
    4 625
    Par défaut
    Citation Envoyé par deadalnix
    Merci pour vos reponses. J'avais en effet cerné ce genre de problemes, comme le fait qu'un mutex ne garantisse rien si les données sont stockées dans des registres.
    Malheureusement, il semblerait que tu n'aies rien compris.
    Que les données soient stockées dans des registres, dans des fichiers, ou que même ce ne soient pas des données que tu verrouilles mais des actions, ça ne change absolument rien.
    Pour simplifier, tu peux considérer qu'un mutex n'est qu'un flag qui indique un état : verrouillé ou non verrouillé. Si tu veux le verrouiller et qu'il est déjà verrouillé, ton thread va bloquer jusqu'à qu'il soit déverrouillé et qu'ainsi tu puisses le verrouiller à ton tour. Voilà, c'est tout. Ça n'a rien à voir avec des variables dans des registres ou quoi que ce soit.
    La seule chose que tu peux faire, c'est utiliser des mutexes pour verrouiller le code qui réalise les accès à ta mémoire partagée entre plusieurs threads.

    volatile ne sert à rien pour le multithreading, ne l'utilise pas. Comme je l'ai déjà dit, ça ne sert qu'à faire lectures/écritures directement depuis la mémoire (donc sans registre). Donc si ta variable fait la taille d'un mot, tu peux avoir de la chance et avoir en plus ces lectures/écritures qui sont atomiques.
    De plus comme ça ne fait de memory barriers que sur Visual C++, ça ne marchera pas sur les architectures avec plusieurs coeurs ou processeurs.

    De toutes manières, tu ne devrais même pas utiliser des mutexes, mais plutôt un framework de transmission de messages, c'est bien plus propre.
    Les mutexes c'est pour le bas niveau, et les instructions atomiques pour le très bas niveau.
    Voir ce message récemment écrit dans un sujet d'à côté : http://www.developpez.net/forums/sho...4&postcount=44
    Boost ftw

  18. #18
    Expert éminent

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Points : 6 911
    Points
    6 911
    Par défaut
    Citation Envoyé par zais_ethael Voir le message
    En absence de doc on peut faire quelque suppositions.
    En l'absence de doc, on ne peut que supposer que ce c'est pas safe.

    (Par exemple, qu'est-ce qui t'indique que la gestion de la memoire par les allocateurs est thread-safe?)
    Les MP ne sont pas là pour les questions techniques, les forums sont là pour ça.

  19. #19
    screetch
    Invité(e)
    Par défaut
    Citation Envoyé par loufoque Voir le message
    volatile ne sert à rien pour le multithreading, ne l'utilise pas. Comme je l'ai déjà dit, ça ne sert qu'à faire lectures/écritures directement depuis la mémoire (donc sans registre). Donc si ta variable fait la taille d'un mot, tu peux avoir de la chance et avoir en plus ces lectures/écritures qui sont atomiques.
    De plus comme ça ne fait de memory barriers que sur Visual C++, ça ne marchera pas sur les architectures avec plusieurs coeurs ou processeurs.
    Salut,


    comem ecrit plus haut j'ai l'impression qu'il y a beaucoup de confusions avec le mot cle volatile. Si tu ne l'utilises pas, tu peux passer du temps sur des donnees partagees sans te rendre compte qu'elles ont changees, je pense que l'exemple que j'ai mis plus haut avec le vector la dessus est tres clair : le compilo stocke la valeur dans le cache et tu es baisé.

    Comme je l'ai dit,

    volatile n'a RIEN A VOIR avec une memory barrier qui lui meme n'a RIEN A VOIR avec un mutex qui a son tour n'a RIEN A VOIR avec du code atomique.

    tu peux travailler sur des mots de maniere atomique, si ils sont dans des registres ca ne sert a RIEN.
    De plus la plupart des operations meme sur les mots ne sont pas atomiques.

    faire A = A+1 revient a charger A, augmenter A, stocker A. Pour le faire "atomiquement" il existe des fonctions de l'API Win32 (voir InterlockedExchange)

    J'insiste sur le fait que la programmation multithread est extremement complexe et on a pas le droit de juste dire
    "volatile on s'en fout" et "si tu as de la chance, les operations seront atomiques" puisque c'est ca qui conduit a des bugs difficiles a trouver.

  20. #20
    Membre émérite
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    1 537
    Détails du profil
    Informations personnelles :
    Localisation : Canada

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 537
    Points : 2 548
    Points
    2 548
    Par défaut
    loufoque, me dire que j'ai rien compris, tout en me proposant ensuite des solutions basées sur la chance, merci bien je m'en passerais. L'objectif du topic est de trouver des solutions, de la doc, etc . . ., et si ton but est de me dire, laisse tomber tu est trop con pour comprendre, passe ton chemin.

    Car oui, j'ai bien compris les problemes (ecriture/lecture memoire dans le desordre, données stockées dans des registres, acces concurrents a des données, etc . . .). Et je ne sais toujours pas comment les résoudre.

    screetch > tu soulèves le problème des bugs. Très bonne question, comment debugguer une application multithread ?

    resumons les outils a notre disposition :
    volatile > oblige le compilos a ne pas faire de supposition sur la valeur d'un variable, et donc de la charger depuis la mémoire a chaque fois qu'il en a besoin.
    memory barrier > les acces et lectures memoire située avant la memory barrier sont fait avant ceux situés apres. Question subsidiaire : qu'est ce qui peut me garantire que le compilo ne chagera pas lui meme l'ordre de ces instructions ?
    mutex, rwlock, semaphores, etc . . . > permet de creer un "controle d'acces" a des ressource partagées. Question subsidiaire : Qui me dit que les données auquels j'ai touchées dans le thread A seront bien remises en memoire au moment ou je libere mon mutex (ils peuvent etre dans le cache processeur, voir meme encore dans ses registrer si le compilos a pas choisis de les mettre en memoire a ce moment la) ?
    code atomique > Alors la en C++, je ne voit meme pas comment faire . . .

    Il y a vraiment personne qui a ecris des tutoriels, des documentations (pour le C++, pas pour le processeur directement, a moins qu'on soit obligé de coller un bout d'asm dans son code C++, mais la, c'est vraiment pas propre du tout !), ou tout autre chose la dessus ?

Discussions similaires

  1. Qt et la programmation multithread
    Par yan dans le forum Multithreading
    Réponses: 1
    Dernier message: 06/10/2011, 23h58
  2. question sur un programme multithread
    Par blueLight dans le forum Windows
    Réponses: 10
    Dernier message: 17/07/2008, 09h23
  3. Programmation multithread pour débutant
    Par oodini dans le forum C++
    Réponses: 5
    Dernier message: 07/03/2008, 14h14
  4. temps d'execution d'un programme multithread
    Par La taupe dans le forum C
    Réponses: 2
    Dernier message: 10/01/2007, 17h44
  5. Programmation multithreads ?
    Par rulianf dans le forum C++
    Réponses: 2
    Dernier message: 03/02/2006, 20h17

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