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 ?
Réponses
Trop de publicités?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:
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.
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.
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.
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.