65 votes

L'accès à une variable en C # est-il une opération atomique?

J'ai été élevé à croire que si plusieurs threads peuvent accéder à une variable, toutes les lit et écrit sur cette variable doit être protégée par le code de synchronisation, comme un "verrou", parce que le processeur peut passer à un autre thread à mi-chemin par le biais de l'écriture.

Cependant, j'ai été à la recherche par le biais de Système.Web.De sécurité.L'adhésion à l'aide de Réflecteur et trouvé un code comme ceci:

public static class Membership
{
    private static bool s_Initialized = false;
    private static object s_lock = new object();
    private static MembershipProvider s_Provider;

    public static MembershipProvider Provider
    {
        get
        {
            Initialize();
            return s_Provider;
        }
    }

    private static void Initialize()
    {
        if (s_Initialized)
            return;

        lock(s_lock)
        {
            if (s_Initialized)
                return;

            // Perform initialization...
            s_Initialized = true;
        }
    }
}

Pourquoi le s_Initialized champ de lecture à l'extérieur de la serrure? Ne pouvait pas un autre thread être en train d'essayer d'écrire en même temps? Sont lit et écrit des variables atomiques?

37voto

John Richardson Points 1197

Pour la réponse définitive d'aller à la spécification. :)

Partition I, Section 12.6.6 de la CLI spec états: "conforme CLI doit garantir que l'accès en lecture et écriture à aligner correctement les emplacements de la mémoire qui n'est pas plus que le natif de la taille de mot est atomique lorsque tous les accès en écriture à un emplacement sont de la même taille."

Donc, qui confirme que s_Initialized ne sera jamais instable, et que de lecture et d'écriture à primitve types sont atomiques.

Verrouillage crée une barrière de mémoire pour empêcher le processeur de la réorganisation des lectures et des écritures. La serrure crée le seul obstacle dans cet exemple.

34voto

Thomas Danecker Points 3127

C'est un (mauvais) forme de la double vérification de verrouillage du motif qui n'est pas thread-safe en C#!

Il y a un gros problème dans ce code:

s_Initialized n'est pas volatile. Cela signifie que l'écrit dans le code d'initialisation peut se déplacer après s_Initialized est définie sur true et les autres threads peuvent voir non initialisée code, même si s_Initialized est vrai pour eux. Cela ne s'applique pas à Microsoft de la mise en œuvre du Cadre parce que chaque écriture est une écriture volatile.

Mais également dans la mise en œuvre Microsoft, lit des données non initialisées peuvent être réorganisés (c'est à dire prefetched par le processeur), donc si s_Initialized est vrai, la lecture des données qui doit être initialisé peut entraîner à lire des vieux, des données non initialisées à cause du cache-hits (ie. les lectures sont réorganisées).

Par exemple:

Thread 1 reads s_Provider (which is null)  
Thread 2 initializes the data  
Thread 2 sets s\_Initialized to true  
Thread 1 reads s\_Initialized (which is true now)  
Thread 1 uses the previously read Provider and gets a NullReferenceException

Le déplacement de la lecture de s_Provider avant la lecture de s_Initialized est tout à fait légal car il n'est pas volatile lire n'importe où.

Si s_Initialized serait volatile, la lecture de s_Provider ne serait pas autorisé à se déplacer avant la lecture de s_Initialized et aussi de l'initialisation du Fournisseur n'est pas autorisé à se déplacer après s_Initialized est définie sur true et tout est ok maintenant.

Joe Duffy a également écrit un Article sur ce problème: Cassé variantes sur une double vérification de verrouillage

12voto

Leon Bambrick Points 10886

Accrochez-la question est dans le titre n'est certainement pas la vraie question que Rory est de demander.

Le titulaire a la simple réponse "Non", mais ce n'est pas aider du tout, quand vous voyez la vraie question, ce que je ne pense pas que quelqu'un a donné une réponse simple.

La vraie question Rory demande est présentée, beaucoup plus tard, et est plus pertinent pour l'exemple qu'il donne.

Pourquoi le s_Initialized champ en lecture à l'extérieur de la serrure?

La réponse est aussi simple, mais complètement étrangers à l'atomicité de la variable d'accès.

Le s_Initialized champ est en lecture à l'extérieur de la serrure parce que les verrous sont chers.

Depuis le s_Initialized champ est essentiellement "écrire une fois," il ne sera jamais de retour d'un faux positif.

Il est économique à le lire à l'extérieur de la serrure.

C'est un faible coût de l'activité avec une forte chance d'avoir un avantage.

C'est pourquoi il est lu à l'extérieur de la serrure -- pour éviter de payer le coût de l'aide d'un verrou, sauf si c'est indiqué.

Si les serrures étaient bon marché, le code serait plus simple, et omettre la première case.

(edit: réponse de nice à partir de rory suit. Ouai, boolean lit sont beaucoup atomique. Si quelqu'un a construit un processeur non-atomique boolean lit, ils seraient en vedette sur la DailyWTF.)

7voto

Rory MacLeod Points 4574

La bonne réponse semble être "Oui, la plupart du temps."

  1. John réponse du référencement de la CLI spec indique que l'accès aux variables d'une taille de 32 bits sur un processeur 32 bits sont atomiques.
  2. Une confirmation supplémentaire de la C# spec, la section 5.5, l'Atomicité des références à des variables:

    Lit et écrit des types de données suivants sont atomiques: bool, char, byte, sbyte, short, ushort, uint, int, float, et les types référence. En outre, les lectures et les écritures des types enum avec un type sous-jacent dans la liste précédente sont également atomique. Lit et écrit des autres types, y compris de long, ulong, double, decimal, ainsi que les types définis par l'utilisateur, ne sont pas garantis pour être atomique.

  3. Le code dans mon exemple était reformulation de l'Appartenance de classe, comme l'écrit l'ASP.NET l'équipe eux-mêmes, de sorte qu'il était toujours sûr de supposer que la façon dont elle accède à la s_Initialized champ est correct. Maintenant, nous savons pourquoi.

Edit: Comme Thomas Danecker points, même si l'accès au terrain est atomique, s_Initialized devrait vraiment être marqué volatile à assurez-vous que le verrouillage n'est pas rompu par le processeur de réordonner les lectures et les écritures.

2voto

John Richardson Points 1197

La fonction d'initialisation est défectueux. Il devrait ressembler à ceci:

private static void Initialize()
{
    if(s_initialized)
        return;

    lock(s_lock)
    {
        if(s_Initialized)
            return;
        s_Initialized = true;
    }
}

Sans la deuxième case à l'intérieur de la serrure, il est possible de l'initialisation du code sera exécuté deux fois. Donc, la première case est pour la performance de sauver la prise d'un verrou inutilement, et de la deuxième vérification est pour le cas où un thread exécute l'initialisation du code, mais n'a pas encore réglé le s_Initialized drapeau et donc un deuxième thread passer la première case et être en attente de la serrure.

Jean.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X