web-dev-qa-db-fra.com

l'attribution de référence est atomique, alors pourquoi est-il nécessaire de verrouiller un échange (objet de référence, objet)?

Dans mon service Web asmx multithread, j'avais un champ de classe _allData de mon propre type SystemData qui se compose de quelques List<T> et Dictionary<T> marqué comme volatile. Les données système (_allData) est actualisé de temps en temps et je le fais en créant un autre objet appelé newData et en remplissant ses structures de données avec de nouvelles données. Quand c'est fait j'assigne juste

private static volatile SystemData _allData

public static bool LoadAllSystemData()
{
    SystemData newData = new SystemData();
    /* fill newData with up-to-date data*/
     ...
    _allData = newData.
} 

Cela devrait fonctionner car l'affectation est atomique et les threads qui ont la référence aux anciennes données continuent de l'utiliser et les autres ont les nouvelles données système juste après l'affectation. Cependant, mon collègue a dit qu'au lieu d'utiliser volatile mot-clé et affectation simple, je devrais utiliser InterLocked.Exchange parce qu'il a dit que sur certaines plateformes, il n'est pas garanti que l'attribution de référence soit atomique. De plus: lorsque je déclare the _allData champ as volatile the

Interlocked.Exchange<SystemData>(ref _allData, newData); 

produit un avertissement "une référence à un champ volatile ne sera pas traitée comme volatile" Que dois-je penser à ce sujet?

99
char m

Il y a de nombreuses questions ici. En les considérant un à la fois:

l'attribution de référence est atomique, alors pourquoi est-il nécessaire de verrouiller un échange (objet de référence, objet)?

L'affectation de référence est atomique. Interlocked.Exchange ne fait pas seulement référence à l'affectation. Il fait une lecture de la valeur actuelle d'une variable, cache l'ancienne valeur et affecte la nouvelle valeur à la variable, le tout comme une opération atomique.

mon collègue a dit que sur certaines plates-formes, il n'est pas garanti que l'attribution de référence soit atomique. Mon collègue avait-il raison?

Non. L'affectation de référence est garantie d'être atomique sur toutes les plateformes .NET.

Mon collègue raisonne à partir de fausses prémisses. Est-ce à dire que leurs conclusions sont incorrectes?

Pas nécessairement. Votre collègue pourrait vous donner de bons conseils pour de mauvaises raisons. Il y a peut-être une autre raison pour laquelle vous devriez utiliser Interlocked.Exchange. La programmation sans verrou est incroyablement difficile et au moment où vous vous éloignez des pratiques bien établies adoptées par des experts dans le domaine, vous êtes dans les mauvaises herbes et vous risquez les pires conditions de course. Je ne suis ni un expert dans ce domaine ni un expert de votre code, je ne peux donc pas porter un jugement dans un sens ou dans l'autre.

produit un avertissement "une référence à un champ volatile ne sera pas traitée comme volatile" Que dois-je penser à ce sujet?

Vous devez comprendre pourquoi c'est un problème en général. Cela permettra de comprendre pourquoi l'avertissement est sans importance dans ce cas particulier.

La raison pour laquelle le compilateur donne cet avertissement est parce que le marquage d'un champ comme volatile signifie "ce champ va être mis à jour sur plusieurs threads - ne générez pas de code qui met en cache les valeurs de ce champ et assurez-vous que les lectures ou écritures de ce champ n'est pas "déplacé vers l'avant et vers l'arrière dans le temps" via les incohérences du cache du processeur. "

(Je suppose que vous comprenez déjà tout cela. Si vous n'avez pas une compréhension détaillée de la signification de volatile et de son impact sur la sémantique du cache du processeur, vous ne comprenez pas comment cela fonctionne et ne devriez pas utiliser volatile. Programmes sans verrou sont très difficiles à obtenir correctement; assurez-vous que votre programme est bon parce que vous comprenez comment il fonctionne, pas par accident.)

Supposons maintenant que vous créez une variable qui est un alias d'un champ volatil en passant une référence à ce champ. A l'intérieur de la méthode appelée, le compilateur n'a aucune raison de savoir que la référence doit avoir une sémantique volatile! Le compilateur générera joyeusement du code pour la méthode qui ne parvient pas à implémenter les règles pour les champs volatils, mais la variable is un champ volatile. Cela peut complètement détruire votre logique sans verrouillage; l'hypothèse est toujours qu'un champ volatil est toujours accessible avec une sémantique volatile. Cela n'a aucun sens de le traiter comme volatil parfois et pas à d'autres moments; vous devez toujours être cohérent sinon vous ne pouvez pas garantir la cohérence sur les autres accès.

Par conséquent, le compilateur vous avertit lorsque vous effectuez cette opération, car cela va probablement complètement gâcher votre logique sans verrouillage soigneusement développée.

Bien sûr, Interlocked.Exchange is écrit pour s'attendre à un champ volatile et faire la bonne chose. L'avertissement est donc trompeur. Je le regrette beaucoup; ce que nous aurions dû faire, c'est implémenter un mécanisme par lequel un auteur d'une méthode comme Interlocked.Exchange pourrait mettre un attribut sur la méthode en disant "cette méthode qui prend une référence applique une sémantique volatile sur la variable, donc supprimez l'avertissement". Peut-être le ferons-nous dans une future version du compilateur.

168
Eric Lippert

Soit votre collègue se trompe, soit il sait quelque chose que la spécification du langage C # ne sait pas.

5.5 Atomicité des références variables :

"Les lectures et écritures des types de données suivants sont atomiques: bool, char, byte, sbyte, short, ushort, uint, int, float et reference types."

Ainsi, vous pouvez écrire dans la référence volatile sans risque d'obtenir une valeur corrompue.

Vous devez bien sûr faire attention à la façon dont vous décidez quel thread doit récupérer les nouvelles données, afin de minimiser le risque que plusieurs threads à la fois le fassent.

10
Guffa

Interlocked.Exchange <T>

Définit une variable du type T spécifié sur une valeur spécifiée et renvoie la valeur d'origine, sous la forme d'une opération atomique.

Il change et retourne la valeur d'origine, c'est inutile car vous ne voulez que le changer et, comme l'a dit Guffa, c'est déjà atomique.

Sauf si un profileur a prouvé qu'il s'agissait d'un goulot d'étranglement dans votre application, vous devriez envisager de supprimer les verrous, il est plus facile de comprendre et de prouver que votre code est correct.

6
Guillaume

Iterlocked.Exchange() n'est pas seulement atomique, il s'occupe également de la visibilité de la mémoire:

Les fonctions de synchronisation suivantes utilisent les barrières appropriées pour garantir l'ordre de la mémoire:

Fonctions qui entrent ou sortent des sections critiques

Fonctions qui signalent les objets de synchronisation

Fonctions d'attente

Fonctions verrouillées

Problèmes de synchronisation et de multiprocesseur

Cela signifie qu'en plus de l'atomicité, il garantit que:

  • Pour le thread qui l'appelle:
    • Aucune réorganisation des instructions n'est effectuée (par le compilateur, l'exécution ou le matériel).
  • Pour tous les fils:
    • Aucune lecture en mémoire effectuée avant cette instruction ne verra la modification apportée par cette instruction.
    • Toutes les lectures après cette instruction verront la modification apportée par cette instruction.
    • Toutes les écritures en mémoire après cette instruction se produiront après que ce changement d'instruction a atteint la mémoire principale (en vidant ce changement d'instruction dans la mémoire principale une fois terminé et en ne laissant pas le matériel vider sa propre temporisation).
2
selalerer