Est-ce que cette règle sur l'usage volatile est stricte?
j'ai vu cette phrase:
la règle générale est, si vous avez variables de type primitif qui doivent être partagé entre plusieurs threads, déclarer ces variables volatile
à partir de cet article , et cette phrase:
En général, toutes les données peuvent être non daté asynchrone devrait être déclaré d'être volatile.
à partir de cette page , considérant maintenant c'est la règle, je voudrais savoir pourrait vous apporter un exemple d'un cas où, malgré l'existence de moteurs asynchrones accès à des données qui ne déclarer que les données volatiles n'a aucune utilisation dans la pratique ou il n'y a pas de tels cas exceptionnel et la règle est stricte.
8 réponses
je me souviens quand cet article a été publié et je me souviens des interminables discussions qui ont suivi sur comp.lang.C.++modérer.
autant que je me souvienne, Andrei détourne le volatile
mot-clé pour l'utiliser à discriminer entre les différents fonction des surcharges. (Voir cet article de Scott Meyers pour une autre idée.) Ce qu'il n'est brillant, dans ce il permet au compilateur de vous attraper si vous gâchez accès protégé et non protégé aux objets (tout comme le compilateur attrape vous devriez essayer de modifier une constante). Mais outre le fait qu'il vous aide, il n'a rien à voir avec la protection réelle accès concurrent aux objets .
Le problème est que 90% des gens ont une coup d'oeil à l'article et tout ce qu'ils voient est volatile
et "threads" dans le même article . Selon leurs connaissances, ils tirent alors soit la conclusion erronée que volatile
est bon pour les fils (vous semblez l'avoir fait) ou ils crient à lui pour conduire les autres à tirer les conclusions erronées.
Très peu de gens semblent être en mesure de lire l'article fond et de comprendre ce qu'il fait.
Je ne peux pas parler pour le réel scénario d'accès asynchrone, puisque je ne suis pas trop bon à la multithreading, mais ce que le volatile
modificateur fait est de dire au compilateur:
"Écoutez, ce peut changer à tout moment, afin de ne pas mettre en cache ou le mettre dans un registre ou de faire quelque chose de fou comme ça, d'accord?"
Il ne protège pas contre les écritures asynchrones, il désactive simplement optimisations invalide si la variable peut être changée par des forces externes.
Edit: Comme exemple potentiel, un qui n'implique pas de multithreading (mais, ne nécessite exceptionnellement code alambiqué;), voici un cas où volatile est important:
volatile bool keepRunning = true;
void Stuff() {
int notAPointer = 0;
notAPointer = (int)(&keepRunning); //Don't do this! Especially on 64-bit processors!
while(keepRunning) {
*(bool*)(notAPointer) = false;
}
printf("The loop terminated!");
}
sans ce modificateur volatile, le compilateur pourrait dire " hey, keepRunning n'est jamais modifié, donc je n'ai même pas besoin de générer du code qui le vérifie!", alors qu'en réalité nous ne faisons que la modifier en secret.
(en réalité, cela fonctionnerait probablement encore sur une construction non optimisée. Et, cela pourrait aussi fonctionner si le compilateur est intelligent et remarque le pointeur pris. Mais, le principe est le même)
pour donner suite à la réponse de Mike, c'est utile dans des cas comme celui-ci (variables globales utilisées pour éviter la complexité dans cet exemple):
static volatile bool thread_running = true;
static void thread_body() {
while (thread_running) {
// ...
}
}
static void abort_thread() {
thread_running = false;
}
selon la complexité de thread_body
, le compilateur peut choisir de mettre en cache la valeur de thread_running
dans un registre lorsque le thread commence à tourner, ce qui signifie qu'il ne remarquera jamais si la valeur change en false. volatile
force le compilateur à émettre un code qui vérifiera la variable thread_running
réelle sur chaque boucle.
je proposerais une règle beaucoup plus stricte mais très utile: si vous ne comprenez pas exactement ce que volatile
fait, ne l'utilisez pas. Utilisez plutôt lock
. Si vous ne comprenez pas exactement ce lock
et comment l'utiliser, ne pas utiliser le multithreading.
je dirais qu'en théorie ces règles sont absolument correctes, et volatiles est nécessaire chaque fois qu'une variable est accessible par 2 threads. (Même en utilisant des Mutex, car ils n'empêchent pas les optimisations des compilateurs.) Mais dans la pratique les compilateurs sont assez bons pour reconnaître les situations où une variable pourrait être modifiée en dehors d'une fonction particulière de sorte que sa valeur ne devrait pas être mise en cache dans les registres. Dans la plupart des cas, la volatilité n'est pas nécessaire.
j'ai fait une fois tester en MSVC en inspectant la sortie de l'assembleur pour différentes situations, et tout ce qu'il a fallu pour empêcher une variable d'être mise en cache était d'avoir une autre fonction écrivant à la même variable ou prenant son adresse. Les variables globales n'ont jamais été optimisées sauf si "l'optimisation du programme entier" était activée (de sorte que le compilateur peut être sûr que la variable n'est pas modifiée dans d'autres unités de traduction).
de même, la norme C++ ne spécifie pas comment volatile devrait fonctionner, vous devez regarder ce qu'un compilateur particulier fait pour une plate-forme particulière. Aussi volatile est un peu subtil et ce qu'il fait, dépend du compilateur et du modèle de mémoire de la plateforme cible. Les serrures sont beaucoup plus intuitives à utiliser.
avant de prendre l'article que vous avez lié à first too seriously, vous pourriez vouloir lire un autre . Dans un post plus tard sur Usenet , Andrei plus tard admis que les parties de l'article original étaient tout simplement faux, et a indiqué à celui-ci comme donnant une vue plus réaliste des choses (bien que notez que le lien qu'il donne à elle là semble être allé rassis).