2010-01-16 9 views
7

C# Etats spécifications dans la section 5.5 qui lit et écrit sur certains types (à savoir bool, char, byte, sbyte, short, ushort, uint, int, float et types référence) sont garantis atomique.Comment C# garantit-il l'atomicité des opérations de lecture/écriture?

Cela a piqué mon intérêt. Comment peux-tu faire ça? Je veux dire, mon humble expérience personnelle m'a seulement montré de verrouiller des variables ou d'utiliser des barrières si je voulais que les lectures et les écritures soient atomiques; ce serait un tueur de performance si cela devait être fait pour chaque lecture/écriture. Et pourtant C# fait quelque chose avec un effet similaire. Peut-être que d'autres langages (comme Java) le font. Je ne sais pas sérieusement. Ma question n'est pas vraiment destinée à être spécifique à une langue, c'est juste que je sais que C# le fait. Je comprends qu'il pourrait avoir à traiter certaines instructions spécifiques du processeur, et ne pas être utilisable en C/C++. Cependant, j'aimerais toujours savoir comment cela fonctionne. Pour dire la vérité, je croyais que les lectures et les écritures pouvaient être non-atomiques dans certaines conditions, comme une CPU pourrait accéder à un emplacement mémoire alors qu'une autre CPU y écrit. Cela se produit-il uniquement lorsque le processeur ne peut pas traiter tout l'objet en même temps, par exemple parce qu'il est trop grand ou parce que la mémoire n'est pas alignée sur la limite correcte?

Répondre

13

La raison pour laquelle ces types ont une atomicité garantie est qu'ils sont tous de 32 bits ou moins. Comme .NET fonctionne uniquement sur les systèmes d'exploitation 32 et 64 bits, l'architecture du processeur peut lire et écrire la valeur entière en une seule opération. Ceci est en contraste avec, par exemple, une Int64 sur une plate-forme de 32 bits qui doit être lue et écrite en utilisant deux opérations de 32 bits.

Je ne suis pas vraiment un mec hardware donc je m'excuse si ma terminologie me donne l'air d'un bouffon mais c'est l'idée de base.

+1

Pour moi, cela semble clair et direct. – Dykam

+0

Meh. Je pensais d'une manière ou d'une autre qu'il était possible pour un autre thread d'accéder à un emplacement de mémoire _durant_ l'opération d'écriture d'un autre processeur, conduisant à des résultats incohérents. Avais-je tort tout ce temps? – zneak

+1

Il y a beaucoup d'autres problèmes que la lecture/écriture atomique. 2 threads opérant sur les mêmes variables peuvent très bien conduire à des résultats non désirés car ils lisent/modifient/écrivent souvent ce qui n'est pas atomique. Il y a aussi des problèmes de visibilité de la mémoire sur les machines multiprocesseurs qui nécessitent un soin particulier. – nos

-4

Vous ne pouvez pas. Même en passant par le langage d'assemblage, vous devez utiliser des opcodes LOCK spéciaux afin de garantir qu'un autre processus de base ou même un processus similaire ne viendront pas anéantir tout votre travail.

+0

Alors, c'est ce que fait la gigue .net dans les coulisses? Ajouter des verrous à toutes les variables non-locales lire ou écrire? – zneak

+0

Je ne prétendrai pas connaître tous les détails. Je parle d'un point de vue général. –

3

Sur les lectures x86 et les écritures sont atomiques de toute façon. Il est pris en charge au niveau du matériel. Cela ne signifie cependant pas que les opérations comme l'addition et la multiplication sont atomiques; ils nécessitent une charge, calculent, puis stockent, ce qui signifie qu'ils peuvent interférer. C'est là que le préfixe de verrouillage entre en jeu.

Vous avez mentionné les barrières de verrouillage et de mémoire; ils n'ont rien à voir avec les lectures et les écritures étant atomiques. Il n'y a aucun moyen sur x86 avec ou sans l'utilisation de barrières de mémoire que vous allez voir une valeur de 32 bits à moitié écrite.

+1

Oui, je connais les multiplications et leurs amis ne sont pas atomiques. Il n'y a jamais eu de confusion à ce sujet de mon côté. Je croyais en quelque sorte qu'une CPU pouvait accéder à la mémoire alors qu'une autre y écrivait, ce qui pouvait conduire à des résultats incohérents sur la lecture. Cela peut-il arriver, et si oui, puis-je faire quelque chose pour que cela n'arrive pas? – zneak

+0

Non, cela ne peut pas arriver. J'ai explicitement déclaré cela déjà. – wj32

4

Il est assez bon marché d'implémenter la garantie d'atomicité sur les cœurs x86 et x64 car le CLR ne promet que de l'atomicité pour les variables de 32 bits ou moins. Tout ce qui est requis est que la variable est correctement alignée et ne chevauche pas une ligne de cache. Le compilateur JIT assure cela en allouant des variables locales sur un décalage de pile aligné sur 4 octets. Le gestionnaire de tas GC fait de même pour les allocations de tas.

Notable est que la garantie CLR n'est pas une très bonne. La promesse d'alignement n'est pas assez bonne pour écrire du code constamment performant pour les tableaux de doubles. Très bien démontré dans this thread. L'interopérabilité avec le code machine qui utilise les instructions SIMD est également très difficile pour cette raison.

+0

Je trouve curieux que l'allocateur de mémoire tente de résoudre le problème d'alignement pour le tableau de doubles en les forçant à la LOH, plutôt que par ex. disant que chaque fois que le GC alloue ou copie un 'double [] 'de 32 éléments ou plus, et que l'espace disponible suivant n'est pas aligné sur 64 bits, il doit d'abord allouer et rejeter un objet factice de 12 octets. Le gaspillage de mémoire de 5% devrait être moins frais que le coût de forcer des choses au LOH. – supercat

2

Oui, C# et Java garantissent que les charges et les magasins de certains types primitifs sont atomiques, comme vous le dites. Ceci est bon marché car les processeurs capables d'exécuter .NET ou la JVM garantissent que les charges et les magasins de types primitifs correctement alignés sont atomiques. Maintenant, ce que ni C# ni Java ni les processeurs qu'ils exécutent ne garantissent, et qui est coûteux, émet des barrières de mémoire afin que ces variables puissent être utilisées pour la synchronisation dans un programme multithread. Cependant, en Java et en C#, vous pouvez marquer votre variable avec l'attribut "volatile", auquel cas le compilateur se charge d'émettre les barrières de mémoire appropriées.