Quand utiliser volatile avec multi threading?

Si deux threads accèdent à une variable globale, de nombreux didacticiels affirment que la variable est volatile pour empêcher le compilateur de mettre la variable en cache dans un registre et ne sera donc pas mis à jour correctement. Cependant, deux threads accédant à une variable partagée sont quelque chose qui appelle une protection via un mutex, n’est-ce pas? Mais dans ce cas, entre le thread verrouillant et libérant le mutex, le code se trouve dans une section critique où seul ce thread peut accéder à la variable, auquel cas la variable n’a pas besoin d’être volatile?

Alors, quelle est l’utilisation / le but de volatile dans un programme multi-thread?

Réponse courte et rapide : volatile est (presque) inutile pour la programmation d’applications multithread, indépendantes de la plateforme. Il ne fournit aucune synchronisation, il ne crée pas de clôtures de mémoire et ne garantit pas l’ordre d’exécution des opérations. Cela ne rend pas les opérations atomiques. Cela ne rend pas votre code comme par magie. volatile peut être l’installation la plus mal comprise dans tout C ++. Voir ceci , ceci et ceci pour plus d’informations sur volatile

D’un autre côté, les produits volatile ont un usage qui n’est peut-être pas si évident. Il peut être utilisé de la même manière que l’on utilise const pour aider le compilateur à vous montrer où vous pourriez faire une erreur en accédant à une ressource partagée de manière non protégée. Cette utilisation est discutée par Alexandrescu dans cet article . Cependant, cela consiste essentiellement à utiliser le système de type C ++ de manière souvent considérée comme un système et à provoquer un comportement indéfini.

volatile était spécifiquement destiné à être utilisé lors de l’interfaçage avec du matériel mappé en mémoire, des gestionnaires de signaux et l’instruction de code machine setjmp. Cela rend volatile directement applicable à la programmation au niveau des systèmes plutôt que la programmation normale au niveau des applications.

Le standard C ++ 2003 ne dit pas que volatile applique tout type de sémantique Acquisition ou Libération sur les variables. En fait, le Standard est totalement silencieux sur toutes les questions relatives au multithreading. Cependant, des plates-formes spécifiques appliquent la sémantique Acquérir et Libérer sur volatile variables volatile .

[Mise à jour pour C ++ 11]

Le standard C ++ 11 reconnaît désormais le multithreading directement dans le modèle de mémoire et la langue, et fournit des fonctions de bibliothèque pour le traiter de manière indépendante de la plate-forme. Cependant, la sémantique du volatile n’a toujours pas changé. volatile n’est toujours pas un mécanisme de synchronisation. Bjarne Stroustrup en dit autant dans TCPPPL4E:

N’utilisez pas volatile sauf dans le code de bas niveau qui traite directement avec le matériel.

Ne pas supposer volatile a une signification particulière dans le modèle de mémoire. Ce ne est pas. Ce n’est pas – comme dans certains langages ultérieurs – un mécanisme de synchronisation. Pour obtenir la synchronisation, utilisez atomic , un mutex ou une condition_variable .

[/ End update]

Ce qui précède s’applique au langage C ++ lui-même, tel que défini par le standard 2003 (et maintenant le standard 2011). Certaines plates-formes spécifiques ajoutent toutefois des fonctionnalités supplémentaires ou des ressortingctions à ce volatile fait la volatile . Par exemple, dans MSVC 2010 (au moins), la sémantique Acquérir et Libérer s’applique à certaines opérations sur volatile variables volatile . Depuis MSDN :

Lors de l’optimisation, le compilateur doit maintenir l’ordre entre les références aux objects volatils ainsi que les références à d’autres objects globaux. En particulier,

Une écriture sur un object volatile (écriture volatile) a la sémantique Release; une référence à un object global ou statique qui se produit avant qu’une écriture sur un object volatile dans la séquence d’instructions se produise avant cette écriture volatile dans le binary compilé.

Une lecture d’un object volatile (lecture volatile) a une sémantique Acquired; une référence à un object global ou statique qui se produit après une lecture de la mémoire volatile dans la séquence d’instructions se produit après cette lecture volatile dans le binary compilé.

Cependant, vous remarquerez peut-être que si vous suivez le lien ci-dessus, il y a un débat dans les commentaires sur le fait de savoir si la sémantique acquise / libérée s’applique ou non dans ce cas.

Volatile est parfois utile pour la raison suivante: ce code:

 /* global */ bool flag = false; while (!flag) {} 

est optimisé par gcc pour:

 if (!flag) { while (true) {} } 

Ce qui est évidemment incorrect si le drapeau est écrit par l’autre thread. Notez que sans cette optimisation, le mécanisme de synchronisation fonctionne probablement (en fonction de l’autre code, certaines barrières de mémoire peuvent être nécessaires) – il n’est pas nécessaire d’avoir un mutex dans 1 scénario producteur – 1 consommateur.

Sinon, le mot-clé volatile est trop étrange pour être utilisable – il ne fournit aucune garantie de mémoire pour les access volatiles et non-volatiles et ne fournit aucune opération atomique – vous ne recevez donc aucune aide du mot-clé volatile .

Vous avez besoin de volatile et éventuellement de locking.

volatile indique à l’optimiseur que la valeur peut changer de manière asynchrone, donc

 volatile bool flag = false; while (!flag) { /*do something*/ } 

lira le drapeau à chaque fois autour de la boucle.

Si vous désactivez l’optimisation ou rendez chaque variable volatile, le programme se comportera de la même façon mais plus lentement. volatile signifie simplement «Je sais que vous venez de le lire et que vous savez ce qu’il dit, mais si je le dis, lisez-le et lisez-le.

Le locking fait partie du programme. Donc, à propos, si vous implémentez des sémaphores, ils doivent être volatiles, entre autres. (N’essayez pas, c’est difficile, vous aurez probablement besoin d’un petit assembleur ou du nouveau matériel atomique, et cela a déjà été fait.)

 #include  #include  #include  using namespace std; bool checkValue = false; int main() { std::thread writer([&](){ sleep(2); checkValue = true; std::cout < < "Value of checkValue set to " << checkValue << std::endl; }); std::thread reader([&](){ while(!checkValue); }); writer.join(); reader.join(); } 

Une fois qu'un intervieweur qui croyait que le volatile est inutile, a soutenu avec moi que l'optimisation ne causerait aucun problème et faisait référence à différents cœurs ayant des lignes de cache séparées et tout ça (ne comprenait pas vraiment à quoi il faisait exactement référence). Mais cette partie du code, compilée avec -O3 sur g ++ (g ++ -O3 thread.cpp -lpthread), montre un comportement indéfini. Fondamentalement, si la valeur est définie avant la vérification du temps, elle fonctionne correctement et si ce n'est pas le cas, elle passe en boucle sans avoir à chercher la valeur (qui a été modifiée par l'autre thread). Fondamentalement, je crois que la valeur de checkValue n'est récupérée qu'une seule fois dans le registre et ne sera jamais vérifiée sous le niveau d'optimisation le plus élevé. Si elle est définie sur true avant la récupération, cela fonctionne bien et sinon, elle passe en boucle. S'il vous plaît corrigez-moi si je me trompe.