web-dev-qa-db-fra.com

Quand utiliser volatile avec multi threading?

S'il y a deux threads accédant à une variable globale, de nombreux didacticiels indiquent que la variable est volatile pour empêcher le compilateur de mettre la variable en cache dans un registre et qu'elle ne soit donc pas mise à jour correctement. Cependant deux threads accédant tous deux à une variable partagée est quelque chose qui appelle à la protection via un mutex n'est-ce pas? Mais dans ce cas, entre le verrouillage du thread et la libération du 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, quel est l'utilisation/le but de volatile dans un programme multi-thread?

118
David Preston

Réponse courte et rapide : volatile est (presque) inutile pour la programmation d'applications multithread indépendante de la plate-forme. Il ne fournit aucune synchronisation, il ne crée pas de barrières de mémoire, ni ne garantit l'ordre d'exécution des opérations. Il ne rend pas les opérations atomiques. Cela ne rend pas votre code sécurisé par magie. volatile peut être la fonction la plus mal comprise de tout C++. Voir this , this and this pour plus d'informations sur volatile

D'un autre côté, volatile a une utilité qui n'est peut-être pas si évidente. Il peut être utilisé de la même manière que l'on utiliserait 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 utilise essentiellement le système de type C++ d'une manière qui est souvent considérée comme un artifice et peut évoquer un comportement indéfini.

volatile a été spécialement conçu pour ê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 qu'à la programmation au niveau des applications normales.

La norme C++ 2003 ne dit pas que volatile applique tout type de sémantique Acquire ou Release sur les variables. En fait, la norme est complètement silencieuse sur toutes les questions de multithreading. Cependant, des plates-formes spécifiques appliquent la sémantique Acquire et Release sur les variables volatile.

[Mise à jour pour C++ 11]

Le standard C++ 11 maintenant le fait reconnaît le multithreading directement dans le modèle de mémoire et le lanuage, et il fournit des facilités de bibliothèque pour le traiter d'une manière indépendante de la plateforme. Cependant, la sémantique de 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 présumez pas que volatile a une signification particulière dans le modèle de mémoire. Ce ne est pas. Ce n'est pas - comme dans certaines langues ultérieures - un mécanisme de synchronisation. Pour obtenir la synchronisation, utilisez atomic, un mutex ou un condition_variable.

[/ Fin de la mise à jour]

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

Lors de l'optimisation, le compilateur doit maintenir l'ordre des références aux objets volatils ainsi qu'aux références à d'autres objets globaux. En particulier,

Une écriture sur un objet volatile (écriture volatile) a la sémantique Release; une référence à un objet global ou statique qui se produit avant une écriture sur un objet volatil dans la séquence d'instructions se produira avant cette écriture volatile dans le binaire compilé.

Une lecture d'un objet volatil (lecture volatile) a la sémantique Acquire; une référence à un objet global ou statique qui se produit après une lecture de mémoire volatile dans la séquence d'instructions se produira après cette lecture volatile dans le binaire compilé.

Cependant, vous pourriez prendre note du fait que si vous suivez le lien ci-dessus, il y a un débat dans les commentaires pour savoir si la sémantique d'acquisition/libération en fait s'applique dans ce cas.

152
John Dibling

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'y a pas besoin de mutex dans 1 scénario producteur - 1 scénario consommateur.

Sinon, le mot-clé volatile est trop bizarre pour être utilisable - il ne fournit aucune garantie d'ordre de mémoire par rapport aux accès volatils et non volatils et ne fournit aucune opération atomique - c'est-à-dire que vous ne recevez aucune aide du compilateur avec le mot-clé volatile sauf la mise en cache de registre désactivée .

30
zeuxcg

Vous avez besoin de volatilité et éventuellement de verrouillage.

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 dans la boucle.

Si vous désactivez l'optimisation ou rendez chaque variable volatile, un programme se comportera de la même manière mais plus lentement. volatile signifie simplement "Je sais que vous venez peut-être de le lire et de savoir ce qu'il dit, mais si je le dis, lisez-le.

Le verrouillage fait partie du programme. Donc, au fait, si vous implémentez des sémaphores, ils doivent entre autres être volatils. (N'essayez pas, c'est difficile, il faudra probablement un petit assembleur ou les nouveaux trucs atomiques, et c'est déjà fait.)

1
ctrl-alt-delor