À quoi sert Interlocked.CompareExchange dans la méthode dapper .net?

.net c# dapper thread-safety

Question

Dans le code dapper dans la méthode Link.TryAdd , il y a le morceau de code suivant:

var snapshot = Interlocked.CompareExchange(ref head, null, null);

Pourquoi est-ce nécessaire au lieu de simple:

var snapshot = head;

les deux lignes ne changent pas la valeur de head , les deux lignes attribuent la valeur head à snapshot . Pourquoi le premier a été choisi sur le second?

Edit: le code auquel je fais référence est ici: https://github.com/SamSaffron/dapper-dot-net/blob/77227781c562e65c167bf7a933d69291d5bdc6f3/Dapper/SqlMapper.cs

Réponse acceptée

Ils veulent faire une lecture volatile mais il n'y a pas de surcharge de Thread.VolatileRead qui prend un paramètre de type générique. L'utilisation de Interlocked.CompareExchange permet d'obtenir le même résultat.

Le problème qu’ils essaient de résoudre est que le compilateur JIT peut optimiser l’affectation à une temp si elle le juge utile. Cela peut entraîner des problèmes de thread si un autre thread mute la référence de la tête alors que le thread en cours l'utilise dans une séquence d'opérations.

Modifier:

Le problème n'est pas qu'une valeur TryAdd est lue au début de TryAdd . Le problème est que sur la ligne 105, ils doivent comparer la tête actuelle à la tête précédente (conservée dans l' snapshot ). S'il existe une optimisation, il n'y a pas de variable d' snapshot contenant la valeur précédente et head est relue à ce point. Il est très probable que CompareExchange réussisse même si head peut avoir changé entre les lignes 103 et 105. Le résultat est qu'un nœud de la liste est perdu si deux threads appellent simultanément TryAdd .


Réponse populaire

Mike Z a raison: cela empêche une optimisation JIT (légale) qui briserait le code.

Ils auraient pu utiliser la structure volatile , cependant: Lisez la head et assignez-la à un champ volatile d'une structure. Ensuite, lisez-le dans ce champ et il est garanti que la lecture sera volatile!

La structure elle-même n'a aucune importance. Tout ce qui compte, c'est qu'un champ volatile a été utilisé pour copier la variable.

Comme ça:

struct VolatileHelper<T> { public volatile T Value; }
...
var volatileHelper = new VolatileHelper<Field>();
volatileHelper.Value = head;
var snapshot = volatileHelper.Value;

Espérons que cela n’a pas de coût d’exécution. Dans tous les cas, le coût est inférieur à une opération verrouillée qui provoque un trafic de cohérence de la mémoire CPU.

En fait, le fait que chaque accès au cache (même en lecture) nécessite un trafic de cohérence de la mémoire en fait un cache lent ! Les opérations verrouillées sont une ressource globale du système qui ne s'adapte pas à davantage de processeurs. Un accès verrouillé utilise un verrou matériel global (par adresse mémoire, mais il n'y a qu'une seule adresse ici).




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi