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 :

RACE CONDITION -> Ordre des variables non respecté lors de la compilation "Optimization: Minimize Size (/O1)"


Sujet :

Threads & Processus C++

  1. #1
    Candidat au Club
    Profil pro
    Inscrit en
    Décembre 2011
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2011
    Messages : 5
    Points : 2
    Points
    2
    Par défaut RACE CONDITION -> Ordre des variables non respecté lors de la compilation "Optimization: Minimize Size (/O1)"
    Bonjour,

    J'ai rencontré récement un problème avec Visual Studio.
    En effet, quand je compile ma source, Visual ne respecte pas l'ordre des variables, ce qui me pose problème en multithreading (race condition).

    Voici un simple exemple :

    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
    BOOL	FuncTest()
    {
    	static	BOOLEAN		bTestEmpty = TRUE;
    	static	BOOL		bTest;
    		DWORD		dwTest;
     
    	if (bTestEmpty == TRUE)
    	{
    		/* ICI, du code qui utilise dwTest ... */
     
    		bTest = ((dwTest) ? TRUE : FALSE);    // Remplie bTest avec la bonne valeur
    		bTestEmpty = FALSE;                   // Met bTestEmpty sur FALSE pour dire que le test a déjà été fait
    	}
     
    	return (bTest);
    };
    Donc logiquement, si l'ordre est respecté, c'est à dire que "bTest" est remplie avant que "bTestEmpty" soit mis sur FALSE, ça ne pose aucun problème pour le multithreading.

    Mais quand je regarde sous OllyDebug, je vois ça:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    10010D0F  CMP BYTE PTR DS:[1002E206],1		// if (bTestEmpty == TRUE)
    10010D16  JNE SHORT 10010D46
     
    // ICI
    // DU CODE QUI UTILISE dwTest				
     
    10010D34  CMP DWORD PTR SS:[ESP],EAX
    10010D37  MOV BYTE PTR DS:[1002E206],0		// bTestEmpty = FALSE;
    10010D3E  SETNE AL
    10010D41  MOV DWORD PTR DS:[10032784],EAX	// bTest = ((dwTest) ? TRUE : FALSE);
    
    10010D46  MOV EAX,DWORD PTR DS:[10032784]	// return (bTest);
    10010D4B  POP ECX
    10010D4C  RETN
    On voit très bien que bTestEmpty est mis sur FALSE avant de remplir bTest.
    Ce qui produit une Race Condition (Si plusieurs threads utilisent la fonction au même moment).

    Je compile mon code avec "Optimization: Minimize Size (/O1)"
    Donc ça doit être pour ça que l'ordre des variables n'est pas respecté.

    Si je rajoute #pragma optimize("", off) avant et #pragma optimize("", on) après ma fonction, ça corrige le problème :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    10010D12  MOVZX EAX,BYTE PTR DS:[1002E206]	// if (bTestEmpty == TRUE)
    10010D19  CMP EAX,1
    10010D1C  JNE SHORT 10010D4D
    
    // ICI
    // DU CODE QUI UTILISE dwTest	
    
    10010D41  MOV DWORD PTR DS:[10032784],EAX	// bTest = ((dwTest) ? TRUE : FALSE);
    10010D46  MOV BYTE PTR DS:[1002E206],0		// bTestEmpty = FALSE;
    
    10010D4D  MOV EAX,DWORD PTR DS:[10032784]	// return (bTest);
    10010D52  LEAVE
    10010D53  RETN
    Mais alors les optimisations pour la taille de ma fonction ne sont pas faites.
    Alors oui, ce n'est pas grave si je désactive les optimisations pour cette fonction mais j'ai plusieurs fonctions ou je fais se genre de manip.

    Donc ma question est :
    Il y a t'il une solution pour que l'ordre des variables soient respectée tout en laissant les optimisations activées ?


    Merci d'avance !
    A+

  2. #2
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,

    En me pinçant le nez :
    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
    BOOL	FuncTest()
    {
    	static volatile	BOOLEAN		bTestEmpty = TRUE;
    	static	volatile BOOL		bTest;
    	DWORD		dwTest;
     
    	if (bTestEmpty == TRUE)
    	{
    		/* ICI, du code qui utilise dwTest ... */
     
    		bTest = ((dwTest) ? TRUE : FALSE);    // Remplie bTest avec la bonne valeur
    		bTestEmpty = FALSE;                   // Met bTestEmpty sur FALSE pour dire que le test a déjà été fait
    	}
     
    	return (bTest);
    };
    Une variable static à l’intérieur d'une fonction la rend impure et par conséquent rend cette fonction fragile face au multithreading (l'ajout de volatile n'arrange rien (à l'impureté)) comme tu as pu t'en rendre compte.

    Mon conseil : utilises un pattern état avec un bon vieux Lock sur le changement d'état.

    Sinon, une petite lecture : C++ and the Perils of Double-Checked Locking

  3. #3
    Candidat au Club
    Profil pro
    Inscrit en
    Décembre 2011
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2011
    Messages : 5
    Points : 2
    Points
    2
    Par défaut
    Merci bien
    static volatile, corrige effectivement le problème.

    Parcontre, que la variable static volatile soit à l'intérieur ou à l'extérieur de la fonction, cela ne change rien au niveau du code généré.
    Je ne vois aucune différence sous OllyDebug.

    Donc c'est si grave que ça ?
    Tu enttends quoi par "impure" ?

    En tout cas je te remercie encore une fois !

  4. #4
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par hartt Voir le message
    Merci bien
    static volatile, corrige effectivement le problème.
    En apparence. Le C++03 n'a pas ne connait pas le multithreading. Rien ne te dit que tu n'auras pas un ordonnancement qui fera réapparaitre un problème de race condition (l'opérateur = n'est pas forcément atomique). La seule façon de vraiment s'en sortir est d'utiliser des objets faits pour (sémaphore, mutex, section exclusives, etc...). Je t'ai proposé volatile comme un hack plus que comme quelque chose que je recommanderais.
    Citation Envoyé par hartt Voir le message
    Parcontre, que la variable static volatile soit à l'intérieur ou à l'extérieur de la fonction, cela ne change rien au niveau du code généré.
    Ici, la portée (globale ou locale) ne va avoir d'influence que sur la visibilité des variables : sont-elles visibles uniquement dans la fonction ou dans le fichier .cpp en entier. Que le code généré soit le même n'est pas forcément surprenant.

    Citation Envoyé par hartt Voir le message
    Donc c'est si grave que ça ?
    Oulàlà. On en a vu finir avec un core dumped pour moins que ça
    Citation Envoyé par hartt Voir le message
    Tu entends quoi par "impure" ?
    Fonction pure chez wiki.

    Une fonction pure va avoir tout un tas de propriété intéressante en particulier la thread-safety.

    Une variable statique, même à l'intérieur d'une fonction, a exactement les mêmes inconvénients qu'une variable globale. Et donc pour les mêmes raisons, vaut mieux les éviter.

  5. #5
    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 : 50
    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
    Attention, volatile est un mauvais hack pour des problèmes de multithread. Ne pas l'utiliser, il ne marche pas en général !

    Un compilateur a le droit de réarranger le code comme il le veut, partout, tant que ça ne change pas le sens. Si tu n'utilises pas de primitives de synchronisation, il ne peut pas deviner que ça risque de changer de sens de réarranger le code. Conclusion : Utilise des primitives de synchronisation, qu'il s'agisse de locks, de fonctions atomiques...

  6. #6
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    Attention, volatile est un mauvais hack pour des problèmes de multithread. Ne pas l'utiliser, il ne marche pas en général !
    Ce n'était peut être pas clair mais c'était aussi le sens de mes réserves.
    Citation Envoyé par JolyLoic Voir le message
    Un compilateur a le droit de réarranger le code comme il le veut, partout, tant que ça ne change pas le sens.
    Enfin, si on se place dans un fil d'exécution, il me semble que, du fait qu'elles sont volatiles, justement réarranger change la sémantique (ou on peut jeter à la poubelle une bonne partie du code embarqué non?). En revanche, volatile ou pas, rien ne dit quand l'exécution peut être suspendue au profit d'un autre thread, d'une it, (ou de quoique ce soit d'autre) et rien n'est dit sur l'atomicité de chacune des opérations. D'où le fait que l'utilisation de volatile ne garantit pas grand chose dans un contexte multithread.

    Ceci dit, j'ai cru comprendre que nous prêchions pour les mêmes solution
    Citation Envoyé par 3DArchi Voir le message
    La seule façon de vraiment s'en sortir est d'utiliser des objets faits pour (sémaphore, mutex, section exclusives, etc...).
    Citation Envoyé par JolyLoic Voir le message
    Utilise des primitives de synchronisation, qu'il s'agisse de locks, de fonctions atomiques...

  7. #7
    Candidat au Club
    Profil pro
    Inscrit en
    Décembre 2011
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2011
    Messages : 5
    Points : 2
    Points
    2
    Par défaut
    J'ai oublié de préciser une chose.
    J'utilise la variable static volatile que dans une seule fonction !

    En gros c'est pour éviter que la fonction refasse des calcules pour rien car elle trouvera toujours le même résultat.

    Un peu comme récupérer la version de Windows, la fonction peut appeler GetVersionEx une fois, calculer la version de windows et stocker le résultat dans une var static puis préciser dans une autre var static que le résultat a déjà été trouvé, donc pas la peine d'appeler à nouveau GetVersionEx lors du prochain appele de la fonction.

    Donc dans ma situation :

    - Soit un thread calcule le résultat et précise qu'il a été calculé. Et le prochain thread aura le résultat déjà calculé.
    - Soit deux threads calculent le résultat en même temps. Et le prochain thread aura le résultat déjà calculé.

    Volatile est parfaitement adapté pour ma fonction.
    Pas besoin que ça soit atomique, car si le résultat est calculé deux fois en même temps, ça ne changera rien.

    C'est pour ça que je ne voulais pas utiliser de système de lock atomic avec EnterCriticalSection par exemple.

  8. #8
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    Attention, volatile est un mauvais hack pour des problèmes de multithread. Ne pas l'utiliser, il ne marche pas en général !
    volatile marche, mais il ne marche que dans le cas où il est prévu qu'il fonctionne. Son but n'est aps de jouer avec l'ordonancement des instructions, mais de forcer l'accès en lecture et en écriture à une variable spécifique, plutôt que de laisser le compilateur choisir si l'accès à cette variable peut être optimisé (par exemple en stockant la valeur dans un registrer).

    Un exemple où volatile peut (et doit) être utilisé :

    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
     
    volatile int variable;
     
    void thread_1()
    {
      sleep(100);
      variable = 0;
    }
     
    void thread_2()
    {
      int iterations = 0;
      variable = 0;
      while (variable) {
        ++iterations;  
      }
      std::cout << "iterations pendant 100 secondes : " << iterations << std::endl;
    }
    Sans le volatile, il y a de fortes chances que variable soit stockée dans un registre, et sa modification dans thread_1() ne sera jamais prise en compte. Résultat : on ne sortira jamais de la boucle dans thread_2().

    volatile permet de résoudre le problème puisque à chaque accès, la variable est relue ou écrite. Du coup, on détecte bien le changement de valeur, et la boucle s'arrête.

    volatile marche donc bien comme prévu ; par contre, on ne va pas lui demander de faire plus que ce pour quoi il est prévu

  9. #9
    Candidat au Club
    Profil pro
    Inscrit en
    Décembre 2011
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2011
    Messages : 5
    Points : 2
    Points
    2
    Par défaut
    Salut Emmanuel Deloget,

    Dans ton exemple, si on avait ajouté "static int variable", ça aurait été le même résultat, non ?
    Puisque static n'utilise pas les registres pour stocker la valeur.

  10. #10
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 279
    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 279
    Points : 11 015
    Points
    11 015
    Par défaut
    volatile a une sémantique détournée dans les derniers VC++. C'est pour cela que, là, ça peut marcher.
    IIRC, les atomic<> seront une meilleure solution.

  11. #11
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par hartt Voir le message
    Salut Emmanuel Deloget,

    Dans ton exemple, si on avait ajouté "static int variable", ça aurait été le même résultat, non ?
    Puisque static n'utilise pas les registres pour stocker la valeur.
    Non. Le problème n'est lié qu'à l'optimiseur. Le compilateur voit que dans thread_2 la variable est lue mais jamais modifiée - donc (si le code est assez court), il va charger la valeur dans un registre et ne plus y toucher par la suite. Que la variable soit statique ou non ne changera rien à ce comportement : à un moment ou à un autre, il faut bien la charger dans un registre pour opuvoir la comparer à une valeur ou faire un calcul.

  12. #12
    Candidat au Club
    Profil pro
    Inscrit en
    Décembre 2011
    Messages
    5
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2011
    Messages : 5
    Points : 2
    Points
    2
    Par défaut
    D'accord, merci

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

Discussions similaires

  1. Ordre des variables dans une table
    Par stefsas dans le forum SAS Base
    Réponses: 6
    Dernier message: 02/09/2014, 10h22
  2. Camembert : ordre des séries non respecté
    Par tony59 dans le forum Jasper
    Réponses: 0
    Dernier message: 15/05/2009, 14h44
  3. Ordre des colonnes non triée alphabetiquement
    Par mouss4rs dans le forum JPA
    Réponses: 4
    Dernier message: 30/05/2008, 16h51
  4. Réarranger l'ordre des variables
    Par ash_rmy dans le forum SAS Base
    Réponses: 2
    Dernier message: 16/03/2008, 13h32
  5. [VBA][02]Détection des variables non utilisées
    Par cluis dans le forum VBA Access
    Réponses: 1
    Dernier message: 22/03/2007, 10h20

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