Pourquoi est-volatils pas jugé utile multithread programmation C ou C++?
Comme l'a démontré cette réponse j'ai posté, j'ai l'impression d'être confus au sujet de l'utilité (ou l'absence) de volatile
en multi-thread contexte de programmation.
Ma compréhension est ceci: toutes les fois qu'une variable peut être modifiée à l'extérieur du flux de contrôle d'un morceau de code en y accédant, cette variable doit être déclarée volatile
. Les gestionnaires de signaux I/O registres, et les variables modifiées par un autre thread constituent de telles situations.
Donc, si vous avez un mondial int foo
, et foo
est lu par un thread et de définir automatiquement par un autre thread (probablement à l'aide d'une instruction machine), le thread de lecture, voit dans cette situation de la même manière, il voit une variable tordu par un gestionnaire de signal ou modifié par un matériel externe condition et, par conséquent foo
doit être déclarée volatile
(ou, pour le multithread situations, accessible avec la mémoire clôturé la charge, ce qui est probablement une meilleure solution).
Comment et où ai-je tort?
- Tous les volatiles n'est dire que le compilateur ne doit pas mettre en cache l'accès à une variable volatile. Il ne dit rien sur serialising un tel accès. Cela a été discuté ici, je ne sais pas combien de fois, et je ne pense pas que cette question va rien ajouter à ces discussions.
- Et encore une fois, une question qui ne le mérite pas, et il a été demandé à de nombreuses reprises avant de obtient upvoted. Vous s'il vous plaît arrêter de faire cela.
- J'ai cherché d'autres questions, et trouvé un, mais tout existant explication que j'ai vu en quelque sorte de ne pas déclencher ce dont j'avais besoin pour vraiment comprendre pourquoi j'ai eu tort. Cette question a suscité une telle réponse.
- Pour une grande étude en profondeur sur ce que les Processeurs faire avec des données (par l'intermédiaire de leurs caches) départ: rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
- En Java
volatile
crée une barrière de mémoire lorsqu'il est lu, de sorte qu'il peut être utilisé comme un des threads du pavillon qu'une méthode est terminée depuis qu'il applique un passe-avant relation avec le code avant que le drapeau a été défini. Ce n'est pas le cas en C. - Java volatile n'a rien à voir avec le C/C++ volatile: Java volatile est définie à l'intérieur du modèle de langage, peut être transformé, optimisé (écrire suivie par la lecture de Java volatiles peuvent être optimisés pour écrire) et de la volatilité complètement éliminé si la volatilité de la variable est prouvable pas partagées entre les threads. En C/C++ volatile est à l'extérieur du modèle de langage, les opérations à la volatilité des objets peuvent avoir des effets visible pour les autres appareils, par définition, pas de transformation de la volatilité est possible, pas même une lecture dont le résultat est ignoré peut être éliminé. Ils n'ont pas le même but.
- C'est ce que je voulais dire par "pas le cas en C", où il peut être utilisé pour écrire dans les registres de matériel, etc., et n'est pas utilisé pour le multithreading, comme il est couramment utilisé dans Java.
Vous devez vous connecter pour publier un commentaire.
Le problème avec
volatile
dans un contexte multithread est qu'il ne fournit pas tous les garanties dont nous avons besoin. Il possède quelques propriétés dont nous avons besoin, mais pas tous d'entre eux, de sorte que nous ne pouvons pas compter survolatile
seul.Cependant, les primitives que nous aurions à utiliser pour le restant propriétés fournissent également ceux qui
volatile
fait, donc c'est effectivement inutile.Thread-safe accès aux données partagées, nous avons besoin d'une garantie que:
volatile
variable comme un drapeau pour indiquer si oui ou non certaines données est prêt à être lu. Dans notre code, nous avons tout simplement mis le drapeau après la préparation des données, de sorte que tous semble amende. Mais que faire si les instructions sont réorganisés de sorte que le drapeau est réglé première?volatile
ne garantit pas le premier point. Il garantit également qu'aucune réorganisation se produit entre les différents volatiles des lectures/écritures. Tous lesvolatile
les accès à la mémoire se fera dans l'ordre dans lequel ils sont spécifiés. C'est tout ce dont nous avons besoin pour cevolatile
est prévu pour: manipuler les I/O de registres ou de matériel mappé en mémoire, mais il ne nous aide pas dans le code multithread, où lavolatile
objet est souvent utilisé pour synchroniser l'accès à des données non volatile. Ces accès peuvent toujours être réorganisées par rapport à lavolatile
ceux.La solution pour la prévention de la réorganisation est d'utiliser un barrière de mémoire, qui indique à la fois pour le compilateur et le CPU qui pas d'accès à la mémoire peuvent être réorganisées à l'échelle de ce point de. Placer ces barrières autour de notre volatile variable permet de garantir que même les non-volatile accède à ne pas être réorganisées à travers le volatil, ce qui nous permet d'écrire thread-safe code.
Cependant, les barrières de la mémoire aussi s'assurer que tous dans l'attente de lectures/écritures sont exécutés lorsque la barrière est atteinte, donc effectivement nous donne tout ce dont nous avons besoin par lui-même, en faisant
volatile
inutiles. On peut tout simplement supprimer levolatile
qualifier entièrement.Depuis C++11, atomique variables (
std::atomic<T>
) de nous donner toutes les garanties correspondantes.volatile
n'est pas assez fort pour être utile.volatile
une véritable barrière de mémoire (prévention de la réorganisation). Qui ne fait pas partie de la norme, de sorte que vous ne pouvez pas compter sur ce problème dans un code portable.volatile
n'est pas suffisante en elle-même.volatile
est toujours inutile pour le multi-thread de programmation. (Sauf sous Visual Studio, où volatile est la barrière de la mémoire d'extension.)volatile
?volatile
empêche optimisations trop.global_x = 5; extern_call(); cout << global_x;
le compilateur ne peut pas le remplacer parcout << 5;
parce queextern_call()
peut avoir modifié la valeur.volatile
mondiale en tant que barrière à l'compilateur réorganisation posent un grave problème de performance? Si, dans un morceau de code tous les endroits qui ont besoin de barrières ont explicitement marqué, permettant un compilateur pour réorganiser les activités à l'échellevolatile
peut avoir certains avantages, mais on a le code qui utilisevolatile
mais n'est pas configuré pour utiliser des barrières compris par le compilateur est d'utiliser, une option pour traitervolatile
comme un compilateur-commande de la barrière permet au compilateur utile d'exécuter ce code.volatile
n'a même pas la force d'une lecture ou d'écriture de mémoire. Il a juste provoque la CPU à se comporter comme si une opération de lecture ou d'écriture a été forcé de mémoire single-threaded code. Et c'est tout ce que la norme requise, car il n'ont pas multi-thread sémantique exigences.volatile
sur instrinsics à assumer les forces externes qui peuvent lire et/ou écrire n'importe quel objet à n'importe quel point dans le temps (1) du code existant qui ne comprend ni le volatile ni obstacles pourraient être fixé plus facilement, dans la plupart des cas, par l'ajout devolatile
que par l'ajout de barrières; (2) certaines plates-formes peuvent exiger des instructions spéciales pour la volatilité des lectures/écritures qui sont différentes de faire la synchronisation, l'accès, et puis un autre de synchronisation. La seule saine d'esprit, raison pour laquelle je peux penser pour C89 de ne pas avoir inclus les obstacles (puisque la mise en œuvre devrait être en mesure d'obtenir les...volatile
fournirait. Dans de nombreux cas, il sera important qu'un groupe d'opérations précède l'autre, mais l'ordre des opérations à l'intérieur de chaque groupe n'a pas d'importance.Vous pouvez également envisager ce à partir de la Le Noyau Linux Documentation.
shared_data
se produire dans leur ordre indiqué que la Norme définit qu'ils ont à faire.void spin_lock() { shared_data = 7; }
et la seule façon de le faire est à la corbeille de la copie conservée dans un registre et relire la valeur de la mémoire. En fait, le compilateur a aussi de déversement qui enregistrent le retour de la mémoire de la première, de sorte quevoid spin_lock() { if (shared_data == 42) abort(); }
voit la valeur. Donc, vous voyez, en le considérant comme régulièrement un appel de fonction ne fait exactement la bonne chose dans le compilateur.volatile
est inutile aussi. Dans tous les cas, l'appel à une fonction dont le corps ne peut pas être vu" comportement va être correct.volatile
qualificatif à des moments où il est surveillé par un mutex. Pour un autoportant mise en œuvre, le plus simple type de mutex construire est un gage de passage de drapeau qui dit qui est propriétaire d'un groupe d'objets, et chaque contexte utilise le drapeau pour passer le jeton tout moment il veut de l'autre contexte d'utilisation de l'objet. Sur une mise en œuvre qui s'abstient de déplacement des autres opérations passé une écriture volatile ou l'avance d'un volatile lire, un seul volatile indicateur peut prendre soin de cela.Je ne pense pas que vous avez tort -- volatile est nécessaire pour garantir que le fil d'Une volonté de voir la variation de la valeur, si la valeur est modifiée par quelque chose d'autre que de fil A. si je comprends bien, la volatilité est essentiellement une façon de dire au compilateur "ne cache pas cette variable dans un registre, au lieu assurez-vous de toujours lire/écrire à partir de la mémoire RAM sur tous les accès".
La confusion est parce que la volatilité n'est pas suffisante pour mettre en œuvre un certain nombre de choses. En particulier, les systèmes modernes, l'utilisation de plusieurs niveaux de mise en cache, moderne, multi-core, faire un peu de fantaisie optimisations au moment de l'exécution, et les compilateurs modernes faire un peu de fantaisie optimisations au moment de la compilation, et tout peut entraîner divers effets secondaires montrant dans un ordre différent de celui que vous attendez si vous avez seulement regardé dans le code source.
Tellement volatile est très bien, aussi longtemps que vous gardez à l'esprit que les "observés" des changements dans la volatilité de la variable ne peut pas se produire au moment même où vous pensez qu'ils seront. Plus précisément, n'essayez pas d'utiliser des variables volatiles comme un moyen de synchroniser ou de l'ordre des opérations dans les threads, car il ne fonctionne pas de manière fiable.
Personnellement, ma principale (seule?) l'utilisation de la volatilité de l'indicateur est comme un "pleaseGoAwayNow" booléen. Si j'ai un thread qui tourne en permanence, je vais vérifier la volatilité des booléens à chaque itération de la boucle, et de sortie si le booléen est jamais vrai. Le thread principal peut alors en toute sécurité nettoyer le thread de travail en définissant le booléen à true, et puis l'appel de pthread_join() pour attendre jusqu'à ce que le thread de travail est allé.
mutex_lock
(et tous les autres de la bibliothèque de fonction) peut modifier l'état de la variable d'indicateur.SCHED_FIFO
, plus statique de priorité que les autres processus/threads dans le système, assez de cœurs, doit être parfaitement possible. Sous Linux, vous pouvez spécifier que les processus en temps réel peut utiliser 100% du temps CPU. Ils ne seront jamais de changement de contexte si il n'y a pas de priorité plus élevée thread/processus et de ne jamais bloquer par I/O. Mais le point est que le C/C++volatile
n'est pas destiné à l'application correcte des données de partage et de synchronisation de la sémantique. Je trouve la recherche pour les cas spéciaux de prouver que le code incorrect peut-être pourrait parfois le travail est inutile.Votre compréhension est vraiment mauvais.
La propriété, que la volatilité des variables, est de "lire et écrire à cette variable sont une partie de perceptible comportement du programme". Cela signifie que ce programme fonctionne (compte tenu de l'utilisation de matériel approprié):
Le problème est, ce n'est pas le bien que nous voulons "thread-safe" quoi que ce soit.
Par exemple, un thread-safe compteur serait juste (linux-kernel-comme le code, je ne sais pas le c++0x équivalent):
C'est atomique, sans barrière de mémoire. Vous devez les ajouter si nécessaire. L'ajout de volatiles ne serait probablement pas aider, parce que cela ne concerne pas l'accès à la proximité de code (par exemple. l'ajout d'un élément à la liste le compteur compte). Certainement, vous n'avez pas besoin de voir le compteur incrémenté à l'extérieur de votre programme, et des optimisations sont encore souhaitables, par exemple.
peut encore être optimisé pour
si l'optimiseur est assez intelligent (on ne change pas la sémantique du code).
volatile
est utile (mais non suffisante) pour mettre en œuvre le concept de base d'un spinlock mutex, mais une fois que vous avez (ou quelque chose de supérieur), vous n'avez pas besoin d'un autrevolatile
.La manière typique de la programmation multithread n'est pas de protéger chaque variable partagée au niveau de la machine, mais plutôt d'introduire de la garde des variables qui guide le flux du programme. Au lieu de
volatile bool my_shared_flag;
vous devriez avoirNon seulement cette encapsuler le "dur", il est fondamentalement nécessaire: C ne comprend pas opérations atomiques nécessaires pour mettre en œuvre un mutex; il a seulement
volatile
de faire des garanties supplémentaires sur ordinaire opérations.Maintenant vous avez quelque chose comme ceci:
my_shared_flag
n'a pas besoin d'être volatile, en dépit d'être cache, parce que&
opérateur).pthread_mutex_lock
est une fonction de la bibliothèque.pthread_mutex_lock
en quelque sorte acquiert cette référence.pthread_mutex_lock
transforme le drapeau commun!volatile
, tandis que de sens dans ce contexte, est étrangère.Pour que vos données puissent être cohérent à un concurrent à l'environnement, vous avez besoin de deux conditions pour postuler:
1) Atomicité je.e si je lire ou écrire des données dans la mémoire alors que les données est en lecture/écrite en une seule passe, et ne peut être interrompu ou soutenu en raison de l'e.g un changement de contexte
2) la Cohérence-je.e l'ordre de lecture/écriture de l'ops doit être vu être le même entre simultané de plusieurs environnements - être que les fils, les machines etc
volatile s'inscrit ni de l'au-dessus - ou, plus particulièrement, le c ou le c++ standard de la volatilité devrait se comporter inclut ni de la ci-dessus.
C'est même pire dans la pratique que certains compilateurs ( comme intel Itanium compilateur ) tentent de mettre en œuvre certains éléments de l'accès simultané des comportements sécuritaires ( j'.e en s'assurant de la mémoire des clôtures ) cependant il n'y a pas de cohérence à l'échelle du compilateur de mise en œuvre et en outre la norme ne l'exige pas la mise en œuvre en premier lieu.
Marquage d'une variable, alors que la volatilité sera juste dire que vous êtes en forçant la valeur à être vidées vers et à partir de la mémoire à chaque fois dans de nombreux cas, juste ralentit votre code que vous avez soufflé votre cache de performance.
c# et java autant que je sache faire la réparation en faisant des volatiles adhérer aux 1) et 2) toutefois, le même ne peut pas être dit pour les compilateurs c/c++ donc, fondamentalement, faire avec elle que vous voyez l'ajustement.
Pour un peu plus en profondeur ( mais pas impartial ) discussion sur le sujet lire cette
La comp.de la programmation.fils FAQ a un classique de l'explication par Dave Butenhof:
M. Butenhof couvre la plus grande partie de la même terre dans ce message usenet:
Tout ce qui est tout aussi applicable à C++.
Selon mon ancienne norme C “Ce qui constitue un accès à un objet volatile qualifiés de type est la mise en œuvre définies par l'”. Donc le compilateur C écrivains pourrait ont choisi d'avoir "volatile" signifie "thread-safe d'accès dans un environnement multi-processus de l'environnement". Mais ils n'ont pas.
Au lieu de cela, les opérations à effectuer pour faire une section critique thread-safe dans un multi-core et multi-processus de la mémoire partagée de l'environnement ont été ajoutés en tant que nouveaux définis par l'implémentation de fonctionnalités. Et, libéré de l'obligation d' "volatile" permettrait atomique et l'accès de la commande dans un multi-processus de l'environnement, le compilateur écrivains prioritaires de code de réduction historique de la mise en œuvre dépendant de la "volatilité" de la sémantique.
Cela signifie que des choses comme "volatile" sémaphores autour de la critique des sections de code qui ne sont pas de travailler sur du nouveau matériel avec de nouveaux compilateurs, ont déjà travaillé avec les anciens compilateurs sur du vieux matériel, et des exemples anciens sont parfois pas mal, juste vieux.
volatile
aurait besoin de le faire, afin de permettre d'écrire un OS dans une manière qui est matériel, mais dépend du compilateur indépendant. En exigeant que les programmeurs utilisent dépend de l'implémentation des fonctionnalités plutôt que de fairevolatile
travail comme l'exige la sape le but d'avoir un standard.C'est tout ce que "volatile" est en train de faire:
"Hey compilateur, cette variable peut changer À TOUT MOMENT (sur n'importe quel tic d'horloge), même si il n'y a PAS d'INSTRUCTIONS agissant sur elle. Ne PAS mettre en cache cette valeur dans un registre."
Qui est-IL. Il indique au compilateur que votre valeur est, bien volatiles, cette valeur peut être modifiée à tout moment par la logique externe (un autre fil, un autre processus, le Noyau, etc.). Il existe plus ou moins exclusivement pour supprimer les optimisations du compilateur qui va silencieusement cache une valeur dans un registre qu'il est intrinsèquement dangereux à JAMAIS cache.
Vous rencontrerez peut-être des articles comme "Dr Dobbs" que le ton volatile que certains panacée pour le multi-thread de programmation. Son approche n'est pas totalement dénuée de fondement, mais il a le défaut fondamental de la réalisation d'un objet, les utilisateurs de responsable de son fil de sécurité, ce qui a tendance à avoir les mêmes problèmes que d'autres violations de l'encapsulation.