Question

Quand j'écris une valeur dans un champ, quelles garanties puis-je obtenir sur le moment où sera sauvegardé la nouvelle valeur dans la mémoire principale? Par exemple, comment puis-je savoir que le processeur ne tient pas la nouvelle valeur dans son cache privé, mais mis à jour la mémoire principale
Un autre exemple:

int m_foo;

void Read() // executed by thread X (on processor #0)
{
   Console.Write(m_foo);
}

void Write() // executed by thread Y (on processor #1)
{
   m_foo = 1;
}

Est-il possible que, après Write () a fini d'exécution, d'autres exécute de fil Lire () comme la valeur actuelle mais seront effectivement voir "0"? (Depuis peut-être l'écriture précédente à m_foo n'a pas été rincée encore?).
Quel genre de primitives (à côté de serrures) sont disponibles pour assurer les les écritures vidées ont été?


EDIT Dans l'exemple de code que j'ai utilisé, la lecture et d'écriture sont placés à autre méthode. N'affecte pas seulement Thread.MemoryBarrier Reording d'instruction qui existent dans la même portée?

En outre, supposons qu'ils ne seront pas inline par le JIT, comment puis-je faire en sorte que la valeur écrite m_foo ne sera pas stocké dans un registre, mais à la mémoire principale? (Ou quand m_foo est lu, il ne sera pas une ancienne valeur du cache du processeur).

Est-il possible d'y parvenir sans utiliser des serrures ou le mot-clé « volatile »? (Aussi, disons que je ne suis pas en utilisant des types primitifs, mais un mot taille struct [si volatile ne peut pas être appliquée] .)

Était-ce utile?

La solution

volatile et Interlocked ont déjà été mentionnés, vous avez demandé primitives, un ajout à la liste est d'utiliser Thread.MemoryBarrier() avant votre écriture ou lit. Cela garantit aucune remise en ordre est fait de la mémoire écrit et lit.

fait « à la main » ce lock, Interlocked et volatile peuvent faire automatiquement la plupart du temps. Vous pouvez l'utiliser comme un remplacement complet à toute autre technique, mais il est sans doute le chemin le plus difficile de voyager, et le dit MSDN:

  

«Il est difficile de construire des programmes multithread corrects en utilisant   MemoryBarrier. , La plupart des cas   C # déclaration de verrouillage, le Visual Basic   déclaration SyncLock, et les méthodes de   la classe de contrôle fournissent plus facile et   des moyens moins sujettes à l'erreur de synchroniser   accède à la mémoire. Nous vous recommandons   les utiliser au lieu de MemoryBarrier. «

Comment utiliser MemoryBarrier

Un très bel exemple sont les implémentations de VolatileRead et VolatileWrite, que les deux utilisent en interne MemoryBarrier. La règle de base à suivre est: lorsque vous lisez une variable, placez une barrière mémoire après la lecture. Lorsque vous écrivez la valeur, la barrière de mémoire doit venir avant l'écriture.

Dans le cas où vous avez des doutes que ce soit moins efficace alors lock, considérer que le verrouillage est rien de plus « escrime complète », en ce qu'elle place une barrière de mémoire avant et après le bloc de code (en ignorant Monitor pour un moment). Ce principe est bien expliqué dans cette excellent article définitif sur les discussions, le verrouillage, les barrières volatiles et de la mémoire par Albahari .

De réflecteur:

public static void VolatileWrite(ref byte address, byte value)
{
    MemoryBarrier();
    address = value;
}

public static byte VolatileRead(ref byte address)
{
    byte num = address;
    MemoryBarrier();
    return num;
}

Autres conseils

Si vous voulez vous assurer qu'il est écrit, puis marquer rapidement et en ordre comme volatile ou (avec plus de douleur) utiliser Thread.VolatileRead / Thread.VolatileWrite (pas une option attrayante et facile à manquer un, ce qui rend inutile).

volatile int m_foo;

Sinon, vous avez pratiquement aucune garantie de quoi que ce soit (dès que vous parlez plusieurs threads).

Vous pouvez également regarder le verrouillage ( Monitor ) ou Interlocked , ce qui permettrait d'atteindre le même effet tant que même l'approche utilisée de tous les accès (ie tous lock , ou tout Interlocked, etc).

Tant que vous n'utilisez pas la synchronisation que vous avez aucune garantie qu'un fil en cours d'exécution sur un processeur voit les modifications apportées par un autre thread en cours d'exécution sur un autre processeur. En effet, la valeur pourrait être mis en cache dans les caches CPU ou dans un registre CPU.

Par conséquent, vous devez soit marquer la variable comme volatile. Cela va créer un « Arrive-Before'-realation entre lit un écrit.

Ce n'est pas un problème de cache du processeur. Les écritures sont pass-through habituellement (écrit aller à la fois pour mettre en cache et de la mémoire principale) et toutes les lectures auront accès à la mémoire cache. Mais il y a beaucoup d'autres caches sur le chemin (de langage de programmation, les bibliothèques, le système d'exploitation, I / O tampons, etc.). Le compilateur peut également choisir de conserver une variable dans un registre de processeur et de ne jamais écrire dans la mémoire principale (c'est ce que l'opérateur volatile est conçu pour, éviter la valeur de stockage dans le registre quand il peut être un E / S mémoire mappée).

Si vous avez plusieurs processus ou plusieurs threads et la synchronisation est un problème que vous devez le faire explicitement, il y a plusieurs façons de le faire selon le cas d'utilisation.

Pour un seul programme fileté, ne se soucient pas, le compilateur fera ce qu'il doit et lit auront accès à ce qui a été écrit.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top