Frage

Ich bin immer noch ein wenig unklar, und wenn eine Sperre um einige Codes zu wickeln. Meine Regel-of-Daumen ist eine Operation in einem Schloss wickeln, wenn sie liest oder schreibt auf eine statische Variable. Aber wenn eine statische Variable nur gelesen wird (zum Beispiel ist es eine Nur-Lese, die bei dem Typ Initialisierung eingestellt ist), es Zugriff muss nicht in einer lock-Anweisung gewickelt wird, nicht wahr? Vor kurzem sah ich einige Code, wie das folgende Beispiel betrachtet, und es hat mich zum Nachdenken kann es einige Lücken in meinem Multithreading Wissen sein:

class Foo
{
    private static readonly string bar = "O_o";

    private bool TrySomething()
    {
        string bar;

        lock(Foo.objectToLockOn)
        {
            bar = Foo.bar;          
        }       

        // Do something with bar
    }
}

Das macht einfach keinen Sinn für mich - warum sollte es von Concurrency Problemen mit einem Register READING

Auch bringt dieses Beispiel eine weitere Frage auf. Ist eine von ihnen besser als die anderen? (Beispielsweise zwei hält die Sperre für weniger Zeit?) Ich glaube, ich könnte die MSIL zerlegen ...

class Foo
{
    private static string joke = "yo momma";

    private string GetJoke()
    {
        lock(Foo.objectToLockOn)
        {
            return Foo.joke;
        }
    }
}

vs.

class Foo
{
    private static string joke = "yo momma";

        private string GetJoke()
        {
            string joke;

            lock(Foo.objectToLockOn)
            {
                joke = Foo.joke;
            }

            return joke;
        }
}
War es hilfreich?

Lösung

Da keiner der Code, den Sie geschrieben haben, das statische Feld nach der Initialisierung ändert, gibt es keine Notwendigkeit für eine Verriegelung. Einfach ersetzen Sie die Zeichenfolge mit einem neuen Wert entweder nicht Synchronisation benötigen, es sei denn, der neue Wert auf die Ergebnisse eines Lese des alten Wertes abhängt.

Statische Felder sind nicht die einzigen Dinge, die Synchronisation benötigen, jede gemeinsame Referenz, die anfällig für Probleme bei der Synchronisierung geändert werden.

class Foo
{
    private int count = 0;
    public void TrySomething()    
    {
        count++;
    }
}

Sie können annehmen, dass zwei Threads die TrySomething Methode Ausführung wäre in Ordnung. Aber es ist nicht.

  1. Thread A liest den Wert von count (0) in ein Register, so kann sie erhöht werden.
  2. Kontextschalter! Der Thread-Scheduler entscheidet Thread A genug Ausführungszeit hatte. Als Nächstes steht Thread B.
  3. Thread B liest den Wert von count (0) in ein Register.
  4. Thread B erhöht das Register.
  5. Thread B speichert das Ergebnis (1) zu zählen.
  6. Kontext wechseln zurück zu A.
  7. Thread A das Register mit dem Wert der Zählung neu geladen (0) auf dem Stack gespeichert.
  8. Thread A erhöht das Register.
  9. Thread A speichert das Ergebnis (1) zu zählen.

Also, auch wenn wir genannt Zählung ++ zweimal hat sich der Wert der Zählung nur von 0 auf 1 gegangen Hiermit kann der Code Thread-sicher machen:

class Foo
{
    private int count = 0;
    private readonly object sync = new object();
    public void TrySomething()    
    {
        lock(sync)
            count++;
    }
}

Nun, wenn Thread A wird unterbrochen Thema B mit Zählung nicht verwirren kann, weil es die Sperre Aussage getroffen und dann blockieren, bis Thread A sync freigegeben hat.

By the way, gibt es eine alternative Art und Weise erhöht wird, um int32s und Int64s thread-safe:

class Foo
{
    private int count = 0;
    public void TrySomething()    
    {
        System.Threading.Interlocked.Increment(ref count);
    }
}

In Bezug auf den zweiten Teil Ihrer Frage, ich glaube, ich würde mit nur gehen je nachdem, was ist leichter zu lesen, wird jede Leistung Unterschied vernachlässigbar sein. Frühe Optimierung ist die Wurzel aller Übel, etc.

Warum Einfädeln schwierig ist,

Andere Tipps

Beim Lesen oder eine 32-Bit oder kleinere Feld zu schreiben ist eine atomare Operation in C #. Es gibt keine Notwendigkeit für eine Sperre in dem Code, den Sie vorgelegt, soweit ich sehen kann.

Es scheint mir, dass das Schloss in Ihrem ersten Fall nicht notwendig ist. Mit Hilfe der statischen Initialisierungsstab initialisieren ist garantiert sicher fädeln. Da Sie immer nur den Wert lesen, gibt es keine Notwendigkeit, es zu sperren. Wenn der Wert nie ändern wird, wird es nie Streit sein, warum Sperre überhaupt?

Dirty Reads?

Meiner Meinung nach, sollten Sie versuchen, sehr schwer nicht statische Variablen in der Lage zu versetzen, wo sie müssen aus verschiedenen Threads gelesen / geschrieben werden. Sie sind im Wesentlichen frei für-alle globalen Variablen in diesem Fall und Globals sind fast immer eine schlechte Sache.

Dass gesagt wird, wenn Sie eine statische Variable in einer solchen Lage versetzt tun, können Sie während eines Lese sperren wollen, nur für den Fall - denken Sie daran, ein anderer Thread in haben swooped kann und verändert den Wert in die Lese, und wenn es der Fall ist, können Sie sich mit korrupten Daten beenden. Liest sind nicht notwendigerweise atomare Operationen, wenn Sie sicher, sie sind durch Sperren. Das Gleiche gilt für schreibt - sie sind nicht immer atomare Operationen entweder

.

Edit: Wie Mark wies darauf hin, für bestimmte Primitive in C # liest immer atomar. Aber seien Sie vorsichtig mit anderen Datentypen.

Wenn Sie nur einen Wert auf einen Zeiger zu schreiben, Sie brauchen nicht zu sperren, da diese Aktion atomar ist. Im Allgemeinen sollten Sie jederzeit sperren Sie brauchen eine Transaktion mit mindestens zwei Atom Aktionen zu tun (liest oder schreibt), die auf dem Zustand hängen nicht zwischen Anfang und Ende zu ändern.

Das heißt - ich aus Java Land kommen, in dem alle Lese- und Schreibvorgänge von Variablen atomare Aktionen. Andere Antworten hier deuten darauf hin, dass .NET anders ist.

Was Ihre Frage „Was ist besser“, sind sie gleich, da der Funktionsumfang nicht für etwas anderes verwendet wird.

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