348 votes

Lorsque le mot clé volatile devrait être utilisé en c# ?

Quelqu'un peut-il fournir une bonne explication du mot clé volatile en c# ? Quels problèmes résout-il et dont il n’est pas ? Dans quels cas elle m’évitera l’utilisation de verrouillage ?

293voto

Ohad Schneider Points 10485

Je ne pense pas que la meilleure personne pour répondre à ce que Eric Lippert (italique dans l'original):

En C#, "volatile" ne signifie pas seulement "assurez-vous que le compilateur et l' gigue de ne pas exécuter n'importe quel code de réorganisation de registre ou de la mise en cache des optimisations sur cette variable". Il signifie aussi "dire les processeurs à faire tout ce qu'ils doivent faire pour s'assurer que je suis la lecture de la dernière valeur, même si cela signifie l'arrêt d'autres processeurs et de faire leur synchroniser la mémoire principale avec leurs caches".

En fait, que le dernier bit est un mensonge. La vraie sémantique de la volatilité des lectures et les écritures sont considérablement plus complexes que ce que j'ai décrit ici; dans fait qu'ils n'ont pas garantir que chaque processeur arrête ce qu'il est en train de faire et les mises à jour des caches vers/depuis la mémoire principale. Plutôt, ils fournissent les plus faibles de la garantie de l'accès à la mémoire, avant et après les lectures et les l'écrit peut être observée à être ordonné à l'égard les uns des autres. Certaines opérations, telles que la création d'un nouveau thread, en entrant dans une serrure, ou à l'aide de l'un des Contrefil famille de méthodes d'introduire une des garanties au sujet de l'observation de la commande. Si vous voulez plus de détails, lire les sections 3.10 et 10.5.3 du C# 4.0 spécification.

Franchement, je vous recommandons de ne jamais faire un champ volatile. Volatile les champs sont un signe que vous sont en train de faire quelque chose de carrément fou: vous êtes à la essayer de lire et d'écrire la même valeur sur les deux threads différents sans mettre un verrou en place. Les verrous de garantir que la lecture de la mémoire ou de modifié à l'intérieur de la serrure est observée pour être cohérent, serrures de garantie qu'un seul thread accède à un morceau de la mémoire à la fois, et ainsi de sur. Le nombre de situations dans lesquelles un verrou est trop lent, c'est très petit, et la probabilité que vous allez obtenir le code de mal parce que vous ne comprenez pas l'exact modèle de mémoire est très grand. J' n'essayez pas d'écrire tout bas-code de verrouillage, sauf pour le plus trivial les usages des opérations verrouillées. Je quitte l'utilisation de "volatile" de véritables experts.

Pour en savoir plus, voir:

54voto

Skizz Points 30682

Si vous voulez obtenir un peu plus technique sur ce mot-clé volatile, considérer le programme suivant (je suis en utilisant DevStudio 2005):

#include <iostream>
void main()
{
  int j = 0;
  for (int i = 0 ; i < 100 ; ++i)
  {
    j += i;
  }
  for (volatile int i = 0 ; i < 100 ; ++i)
  {
    j += i;
  }
  std::cout << j;
}

En utilisant le standard optimisé (libération) paramètres de compilation, le compilateur crée l'assembleur suivant (IA32):

void main()
{
00401000  push        ecx  
  int j = 0;
00401001  xor         ecx,ecx 
  for (int i = 0 ; i < 100 ; ++i)
00401003  xor         eax,eax 
00401005  mov         edx,1 
0040100A  lea         ebx,[ebx] 
  {
    j += i;
00401010  add         ecx,eax 
00401012  add         eax,edx 
00401014  cmp         eax,64h 
00401017  jl          main+10h (401010h) 
  }
  for (volatile int i = 0 ; i < 100 ; ++i)
00401019  mov         dword ptr [esp],0 
00401020  mov         eax,dword ptr [esp] 
00401023  cmp         eax,64h 
00401026  jge         main+3Eh (40103Eh) 
00401028  jmp         main+30h (401030h) 
0040102A  lea         ebx,[ebx] 
  {
    j += i;
00401030  add         ecx,dword ptr [esp] 
00401033  add         dword ptr [esp],edx 
00401036  mov         eax,dword ptr [esp] 
00401039  cmp         eax,64h 
0040103C  jl          main+30h (401030h) 
  }
  std::cout << j;
0040103E  push        ecx  
0040103F  mov         ecx,dword ptr [__imp_std::cout (40203Ch)] 
00401045  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 
}
0040104B  xor         eax,eax 
0040104D  pop         ecx  
0040104E  ret              

En regardant la sortie, le compilateur a décidé d'utiliser le registre ecx pour stocker la valeur de la variable j. Pour les non-volatile de la boucle (la première) le compilateur a assigné je dans le registre eax. Assez simple. Il ya un couple de bits intéressants - le lea ebx,[ebx] instruction est en fait un multi-octets nop instructions pour que la boucle sauts de 16 octets aligné adresse mémoire. L'autre est l'utilisation de edx pour incrémenter le compteur de la boucle au lieu d'utiliser un inc eax instruction. Les ajouter reg,reg instruction a un temps de latence inférieur à quelques IA32 cœurs par rapport à l'inc reg instruction, mais jamais a une latence plus élevée.

Maintenant, pour la boucle à la volatilité de compteur de boucle. Le compteur est stocké sur [esp] et le mot clé volatile indique au compilateur que la valeur doit toujours être en lecture/écrite à la mémoire et ne jamais attribué à un registre. Le compilateur va même jusqu'à ne pas faire une charge/augmentation/store que trois étapes distinctes (charge eax, inc eax, enregistrer eax) lors de la mise à jour de la contre-valeur, au lieu de la mémoire est directement modifiée en une seule instruction (un complément mem,reg). La façon dont le code a été créé assure que la valeur du compteur de boucle est toujours up-to-date dans le cadre d'un seul cœur de PROCESSEUR. Aucune opération sur les données peut entraîner la corruption ou la perte de données (donc, pas moyen de la charge/inc/store depuis la valeur peut changer au cours de l'inc a donc perdu sur le store). Depuis les interruptions peuvent uniquement être traitées une fois l'instruction terminée, les données ne peuvent jamais être endommagé, même avec des non alignés de la mémoire.

Une fois que vous introduisez un deuxième PROCESSEUR dans le système, le mot clé volatile ne pas en garde contre les données en cours de mise à jour par un autre CPU en même temps. Dans l'exemple ci-dessus, vous devriez avoir les données pour être non alignés pour obtenir une possibilité de corruption. Le mot clé volatile de ne pas prévenir les risques de corruption, si les données ne peuvent être traitées de façon atomique, par exemple, si le compteur de la boucle est de type long long (64 bits) puis il faudrait deux 32 bits opérations de mise à jour de la valeur, au milieu de laquelle une interruption peut se produire et modifier les données.

Donc, le mot clé volatile est seulement bon pour les données alignées qui est inférieure ou égale à la taille de la patrie, de registres, tels que les opérations sont toujours atomique.

Le mot clé volatile a été conçu pour être utilisé avec les opérations d'e / s où l'OI serait en constante évolution, mais avait une constante de l'adresse, comme une mémoire mappée UART de l'appareil, et le compilateur ne devrait pas empêcher la réutilisation de la première valeur lue à partir de l'adresse.

Si vous êtes à la manipulation de données de grande taille ou plusieurs Processeurs, alors vous aurez besoin d'un niveau supérieur (OS) système de verrouillage pour gérer l'accès aux données correctement.

29voto

Benoit Points 12985

Parfois, le compilateur va optimiser un champ et utiliser un registre pour stocker. Si le thread 1 fait une écriture sur le terrain et un autre thread y accède, car la mise à jour a été stocké dans un registre (et pas de mémoire), le 2ème fil obtiendrait des données périmées.

Vous pouvez considérer le mot clé volatile que de dire au compilateur « I want you pour stocker cette valeur dans la mémoire ». Cela garantit que le thread 2ème récupère la dernière valeur.

23voto

Dr. Bob Points 263

Depuis le site MSDN: le modificateur volatile est généralement utilisé pour un champ auquel accessible plusieurs threads sans utiliser l’instruction lock pour sérialiser l’accès. Le modificateur volatile s’assure qu’un seul thread récupère la valeur à jour écrite par un autre thread.

14voto

Joseph Daigle Points 16429

Le CLR aime à optimiser les instructions, de sorte que lorsque vous accédez à un champ dans le code, il peut ne pas toujours accès à la valeur actuelle du champ (il sera peut-être de la pile, etc). Marquage d'un champ volatile garantit que la valeur actuelle du terrain est accessible par l'instruction. Ceci est utile lorsque la valeur peut être modifiée dans un non-verrouillage scénario) par un des threads dans votre programme ou de quelque autre code qui s'exécute dans le système d'exploitation.

De toute évidence vous perdre un peu d'optimisation, mais il ne garder le code plus simple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X