Frage

Ich habe erzogen worden zu glauben, dass, wenn mehrere threads Zugriff auf eine variable ist, dann alle lese-und Schreiboperationen auf diese variable muss geschützt werden durch Synchronisations-code, wie beispielsweise die "lock" - Anweisung, da der Prozessor könnte der Wechsel zu einem anderen thread auf halbem Weg durch ein schreiben.

Allerdings war ich auf der Suche durch das System.Web.Sicherheit.Mitgliedschaft mit dem Reflektor gefunden und code wie diese:

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;
        }
    }
}

Warum ist die s_Initialized Feld Lesen außerhalb des Schlosses?Konnte nicht ein anderer thread versucht zu schreiben, um es an der gleichen Zeit? Sind lese-und Schreibvorgänge von Variablen atomar?

War es hilfreich?

Lösung

Für die definitive Antwort gehen Sie zu der spec.:)

Partition I, Abschnitt 12.6.6 die CLI-Spezifikation besagt:"Eine konforme CLI haftet dafür, dass lese-und Schreibzugriff auf richtig ausgerichtet Speicherplätze, die nicht größer als die native Wortbreite ist atomar, wenn alle Schreibzugriffe auf eine Lage, die die gleiche Größe."

Damit wird bestätigt, dass s_Initialized nie instabil sein, und das Lesen und schreibt primitve Typen, die kleiner als 32 bits werden von atomic.

Insbesondere double und long (Int64 und UInt64) sind nicht garantiert atomar auf einem 32-bit-Plattform.Sie können die Methoden verwenden, die auf die Interlocked Klasse um diese zu schützen.

Zusätzlich, während lese-und Schreibvorgänge atomar ist, gibt es eine race-Bedingung mit addition, Subtraktion, und Inkrementieren und Dekrementieren primitive Typen, denn Sie muss gelesen werden, betrieben und umgeschrieben.Die interlocked-Klasse können Sie zu schützen, diese mit Hilfe der CompareExchange und Increment Methoden.

Stellwerk erzeugt eine memory-Barriere, um zu verhindern, dass der Prozessor von der Neuordnung liest und schreibt.Die Sperre schafft den nur erforderlich Barriere in diesem Beispiel.

Andere Tipps

Dies ist eine (schlechte) form des double-check-locking-Muster, das ist nicht thread-safe in C#!

Es ist ein großes problem in diesem code:

s_Initialized nicht flüchtig ist.Das bedeutet, dass Schreibvorgänge in den Initialisierungs-code verschieben können nach s_Initialized ist auf true festgelegt, und die anderen threads sehen kann, nicht initialisierte code, auch wenn s_Initialized ist wahr für Sie.Dies gilt nicht für die Microsoft-Implementierung des Framework, da jeder schreiben, ist eine flüchtige schreiben.

Aber auch in die Microsoft-Implementierung, liest der nicht initialisierte Daten neu angeordnet werden können (d.h.zuvor abgerufener von der cpu) ist, also wenn s_Initialized wahr ist, das Lesen der Daten, die initialisiert werden können Ergebnis in der Lektüre alten, nicht initialisierte Daten, die aufgrund von cache-hits (ie.die Lesevorgänge sind nachbestellt).

Zum Beispiel:

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

Bewegen Sie das Lesen von s_Provider vor dem Lesen der s_Initialized ist vollkommen legal, da es keine flüchtigen überall Lesen.

Wenn s_Initialized würde flüchtig sein, das Lesen von s_Provider wäre nicht erlaubt zu bewegen, bevor das Lesen von s_Initialized und auch die Initialisierung der Anbieter ist nicht erlaubt zu bewegen, nachdem s_Initialized auf true gesetzt ist und alles ist ok jetzt.

Joe Duffy schrieb auch einen Artikel über dieses problem: Gebrochen Varianten an double-checked-locking

Hängen über-die Frage, die im Titel ist definitiv nicht die eigentliche Frage, Rory bittet.

Die titelgebende Frage ist die einfache Antwort "Nein" - aber das ist überhaupt keine Hilfe, wenn Sie sehen, die eigentliche Frage-was, wie ich glaube nicht, dass jemand geschenkt hat, eine einfache Antwort zu.

Die eigentliche Frage Rory fragt präsentiert wird, viel später und ist mehr relevant für das Beispiel, das er gibt.

Warum ist die s_Initialized Feld Lesen außerhalb des Schlosses?

Die Antwort ist auch einfach, obwohl völlig unabhängig von der Unteilbarkeit der Variablen zugreifen.

Die s_Initialized Feld gelesen wird, die außerhalb des Schlosses, weil Schlösser sind teuer.

Da die s_Initialized Feld ist im wesentlichen "write once" - es wird nie wieder ein false positive ist.

Es ist wirtschaftlich, es zu Lesen außerhalb der Sperre.

Dies ist eine niedrige Kosten Aktivität mit einem hoch chance haben, ein Vorteil.

Das ist, warum es ist zu Lesen außerhalb des Schlosses-zu vermeiden die Zahlung der Kosten für die Verwendung von eine Sperre, es sei denn, es ist angegeben.

Die Schlösser waren Billig, der code wäre einfacher, und weglassen, die ersten check.

(Bearbeiten:schöne Antwort von rory folgt.Yeh, boolean liest sehr viel atomic.Wenn jemand baute ein Prozessor mit nicht-atomaren boolean liest, Sie wären auf der vorgestellten DailyWTF.)

Die richtige Antwort scheint zu sein, "ja, meistens."

  1. John ' s Antwort verweisen auf die CLI-Spezifikation gibt an, dass Zugriffe auf Variablen, die nicht größer ist als 32-bit-auf eine 32-bit-Prozessor sind atomar.
  2. Eine weitere Bestätigung aus der C# - Spezifikation, Abschnitt 5.5, Atomarität von Variablen Referenzen:

    Liest und schreibt die folgenden Datentypen sind atomar:bool, char, byte, sbyte, short, ushort, uint, int -, float-und Referenz-Typen.Darüber hinaus liest und schreibt von enum-Typen mit einer zugrunde liegenden Typ in der obigen Liste sind auch atomar.Lese-und Schreibvorgänge von anderen Arten, einschließlich long, ulong, Doppel -, und dezimal -, als auch Benutzer-definierte Typen sind nicht garantiert atomar.

  3. Der code in meinem Beispiel wurde paraphrasiert aus der Membership-Klasse, wie geschrieben, durch die ASP.NET team selbst, so war es immer sicher davon ausgehen, dass die Art und Weise es greift auf die s_Initialized Feld korrekt ist.Jetzt wissen wir, warum.

Edit:Als Thomas Danecker Punkte aus, auch wenn Sie den Zugriff der Bereich ist atomic, s_Initialized sollte wirklich markiert volatile um sicherzustellen, dass die Verriegelung nicht gebrochen durch den Prozessor Neuordnung der lese-und Schreibvorgänge.

Die Initialize-Funktion ist defekt.Es sollte Aussehen wie dieses:

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

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

Ohne den zweiten check in das Schloss es ist möglich, die Initialisierung der code wird zweimal ausgeführt.Also, die erste Prüfung ist für die performance zu speichern, Sie nehmen eine Sperre unnötig, und die zweite überprüfung wird für den Fall, in dem ein thread ausgeführt wird, ist die Initialisierung code aber noch nicht festlegen s_Initialized Flagge und so einen zweiten thread übergeben würde die erste Prüfung, und wartet vor dem Schloss.

Liest und schreibt Variablen nicht atomar.Sie müssen verwenden-Synchronisation APIs zu emulieren die Atomare liest/schreibt.

Für eine ehrfürchtige Verweis auf diese und viele weitere Fragen im Zusammenhang mit Parallelität, stellen Sie sicher, schnappen Sie sich eine Kopie von Joe Duffy ' s neueste Spektakel.Es ist ein ripper!

"Ist der Zugriff auf eine variable in C# eine Atomare operation?"

Nope.Und es ist nicht ein C# - Sache, noch ist es selbst ein .Netto-Ding, es ist ein Prozessor der Sache.

OJ ist vor Ort auf, dass Joe Duffy ist der Kerl zu gehen, um für diese Art von info.Und "verriegelt" ist eine große Suchbegriff zu verwenden, wenn Sie wollen mehr wissen.

"Torn liest" können auf jeder beliebigen Wert, dessen Felder hinzufügen von bis zu mehr als die Größe eines Zeigers.

@Leon
Ich sehe Ihren Punkt - die Art und Weise ich habe Sie gefragt, und dann kommentiert, die Frage ermöglicht es genommen zu werden in ein paar verschiedene Möglichkeiten.

Um klar zu sein, ich wollte wissen, ob es sicher war, zu gleichzeitigen threads, die Lesen und schreiben Sie an ein Boolesches Feld, ohne eine explizite Synchronisierung code, d.h., der Zugriff auf ein boolean (oder andere primitive-typisierten Variablen atomar.

Dann habe ich die Mitgliedschaft-code ein konkretes Beispiel, aber das eingeführt, eine Reihe von Ablenkungen, wie Sie das double-check-locking, ist die Tatsache, dass s_Initialized ist nur je einmal, und ich bemerkte aus den Initialisierungs-code selbst.

Mein schlechtes.

Sie könnten auch schmücken s_Initialized mit dem volatile-Schlüsselwort und verzichten auf die Verwendung von Schloss völlig.

Das ist nicht korrekt.Werden Sie noch begegnen den problem von einem zweiten thread übergeben den Scheck, bevor der erste thread hat, hatte eine chance, um die Flagge, die in mehrere Ausführungen der Initialisierung code.

Ich denke, du hast gefragt, ob s_Initialized könnte in einem instabilen Zustand, wenn man Sie außerhalb der Sperre.Die kurze Antwort ist Nein.Eine einfache Zuordnung/Lesen wird Kochen zu einer einzigen Montage Anweisungen, die von atomic auf jedem Prozessor die ich mir denken kann.

Ich bin mir nicht sicher, was der Fall ist, die für die Zuordnung zu 64-bit-Variablen, es kommt auf den Prozessor, würde ich davon ausgehen, dass es ist nicht atomar, aber es ist wahrscheinlich auf modernen 32-bit-Prozessoren und sicher auf allen 64-bit-Prozessoren.Zuordnung von komplexen Datentypen werden nicht atomar.

Ich dachte, Sie waren - ich bin mir nicht sicher, ob der Punkt des lock-in deinem Beispiel, es sei denn, Sie sind auch etwas tun, um s_Provider zur gleichen Zeit - dann ist die Sperre wäre sicherzustellen, dass diese Anrufe geschehen zusammen.

Nicht, dass //Perform initialization Kommentar cover erstellen s_Provider?Zum Beispiel

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

    lock(s_lock)
    {
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}

Sonst, die statische Eigenschaft-erhalten ist nur noch null zurück, sowieso.

Vielleicht Verriegelt Aufschluss gibt.Und sonst diese ein ich ziemlich gut.

Hätte ich geahnt, dass Ihr nicht atomar.

Um Ihren code zu immer arbeiten auf schwach bestellt Architekturen, müssen Sie eine MemoryBarrier bevor Sie mit dem schreiben s_Initialized.

s_Provider = new MemershipProvider;

// MUST PUT BARRIER HERE to make sure the memory writes from the assignment
// and the constructor have been wriitten to memory
// BEFORE the write to s_Initialized!
Thread.MemoryBarrier();

// Now that we've guaranteed that the writes above
// will be globally first, set the flag
s_Initialized = true;

Der Speicher schreibt, dass passieren in der MembershipProvider-Konstruktor und schreiben zu s_Provider sind nicht garantiert passieren wird, bevor Sie schreiben s_Initialized, schwach bestellt Prozessor.

Eine Menge Gedanken in diesem thread darum, ob etwas atomar ist oder nicht.Das ist nicht das Problem.Das Problem ist die Reihenfolge, dass Sie Ihre thread schreibt, sind sichtbar für andere threads.Auf schwach bestellt Architekturen, schreibt in den Speicher treten nicht in Ordnung ist und DAS ist die eigentliche Frage nicht, ob eine variable passt innerhalb der Daten-bus.

EDIT: Eigentlich bin ich Misch-Plattformen in meine Aussagen.In C# und die CLR-Spezifikation erfordert, dass schreibt, sind Global sichtbar, in der richtigen Reihenfolge (durch die Verwendung von teuer, store Anweisungen für jedes Geschäft, wenn notwendig).Daher, Sie brauchen nicht, um tatsächlich zu haben, dass der Speicher Barriere dar.Jedoch, wenn es C oder C++, wo keine solche Garantie der globalen Sichtbarkeit Reihenfolge existiert, und Ihre Ziel-Plattform kann haben schwach bestellt Speicher, und es ist multithreaded, dann würden Sie brauchen, um sicherzustellen, dass die Konstruktoren schreibt, sind weltweit sichtbares, bevor Sie das update s_Initialized, die getestet wird, die außerhalb der Sperre.

Ein If (itisso) { überprüfen Sie auf einen booleschen atomar ist, aber auch wenn es nicht war es ist keine Notwendigkeit zu sperren, die ersten check.

Wenn jeder thread hat sich erledigt die Initialisierung dann wird es wahr sein.Es spielt keine Rolle, wenn mehrere threads überprüfen auf einmal.Sie alle bekommen die gleiche Antwort, und, es gibt keinen Konflikt.

Der zweite überprüfen Sie das innere der Sperre ist notwendig, weil ein anderer thread möglicherweise packte die sperren erste und beendet die Initialisierung bereits.

Was Sie Fragen ist, ob der Zugriff auf ein Feld in einer Methode mehrere Male atomic-zu denen die Antwort ist Nein.

In dem obigen Beispiel, den Sie initialisieren routine fehlerhaft ist, wie es kann Ergebnis in mehreren Initialisierung.Würden Sie brauchen, um zu überprüfen die s_Initialized Flagge im inneren der Schleuse als auch außerhalb, um zu verhindern, dass eine race-Bedingung, in denen mehrere threads Lesen s_Initialized Flagge, bevor Sie tatsächlich die Initialisierung code.E. g.,

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

    lock(s_lock)
    {
        if (s_Initialized)
            return;
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}

Ack, nevermind...wie aufgezeigt, ist dies in der Tat falsch.Es kann nicht verhindern, dass ein zweiter thread die Eingabe der "initialize" - code Abschnitt.Bah.

Sie könnten auch schmücken s_Initialized mit dem volatile-Schlüsselwort und verzichten auf die Verwendung von Schloss völlig.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top