Einfrieren eines Eis am Stiel in .NET (eine Klasse unveränderlich machen)

Ich entwerfe eine Klasse, die ich schreibgeschützt machen möchte, nachdem ein Hauptthread fertig konfiguriert ist, d. H. "Einfrieren". Eric Lippert nennt dasEis am Stiel Unveränderlichkeit. Nachdem es eingefroren ist, können mehrere Threads gleichzeitig auf es zugreifen, um es zu lesen.

Meine Frage ist, wie man das threadsicher schreibtrealistisch effizient, d. h. ohne zu versuchen, unnötig zu seinklug.

Versuch 1:

public class Foobar
{
   private Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   // Only intended to be called by main thread, so checks if class is frozen. If it is the operation is invalid.
   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }

   public Object ReadSomething()
   {
      return it;
   }
}

Eric Lippert scheint vorzuschlagen, dass dies in Ordnung wärediese Post. Ich weiß, dass Schriften eine Release-Semantik haben, aber soweit ich das verstehe, bezieht sich dies nur aufBestellungDies bedeutet nicht unbedingt, dass alle Threads den Wert unmittelbar nach dem Schreiben sehen. Kann das jemand bestätigen? Dies würde bedeuten, dass diese Lösung nicht threadsicher ist (dies kann natürlich nicht der einzige Grund sein).

Versuch 2:

Das obige, aber mitInterlocked.Exchange um sicherzustellen, dass der Wert tatsächlich veröffentlicht wird:

public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (_isFrozen == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

Vorteil hierbei wäre, dass wir sicherstellen, dass der Wert veröffentlicht wird, ohne den Overhead bei jedem Lesevorgang zu erleiden. Wenn keiner der Lesevorgänge vor dem Schreiben in _isFrozen verschoben wird, da die Interlocked-Methode eine vollständige Speichersperre verwendet, ist dies vermutlich threadsicher. Wer weiß jedoch, was der Compiler tun wird (und gemäß Abschnitt 3.10 der C # -Spezifikation scheint das ziemlich viel zu sein), also weiß ich nicht, ob dies threadsicher ist.

Versuch 3:

Lesen Sie auch mitInterlocked.

public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (Interlocked.CompareExchange(ref _isFrozen, 0, 0) == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

Auf jeden Fall threadsicher, aber es scheint ein wenig verschwenderisch zu sein, bei jedem Lesevorgang den Vergleichstausch durchführen zu müssen. Ich weiß, dieser Aufwand ist wahrscheinlich minimal, aber ich bin auf der Suche nach einemvernünftig effiziente Methode (obwohl vielleicht das es ist).

Versuch 4:

Verwendenvolatile:

public class Foobar
{
   private volatile Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }
}

Aber Joe Duffy erklärte "sayonara flüchtig", daher halte ich das nicht für eine Lösung.

Versuch 5:

Alles sperren, wirkt etwas übertrieben:

public class Foobar
{
   private readonly Object _syncRoot = new Object();
   private Boolean _isFrozen;

   public void Freeze() { lock(_syncRoot) _isFrozen = true; }

   public void WriteValue(Object val)
   {
      lock(_syncRoot) // as above we could include an attempt that reads *without* this lock
         if (_isFrozen)
            throw new InvalidOperationException();

      // write ...
   }
}

Scheint auch definitiv threadsicher zu sein, hat aber mehr Overhead als die Verwendung des Interlocked-Ansatzes oben, daher würde ich Versuch 3 diesem vorziehen.

Und dann kann ich mir wenigstens ein paar mehr einfallen lassen (ich bin mir sicher, dass es noch viele mehr gibt):

Versuch 6: benutzenThread.VolatileWrite undThread.VolatileRead, aber diese sind angeblich etwas schwerfällig.

Versuch 7: benutzenThread.MemoryBarrierscheint auch ein wenigintern.

Versuch 8: erstelle eine unveränderliche Kopie - will das nicht

Zusammenfassend:

Welchen Versuch würdest du machen und warum (oder wie würdest du es machen, wenn es ganz anders wäre)? (d. h. was ist der beste Weg, um einen Wert zu veröffentlichen, der dann gleichzeitig gelesen wird, während er einigermaßen effizient ist, ohne übermäßig "clever" zu sein?)Bedeutet die "Release" -Semantik des .NET-Speichermodells, dass alle anderen Threads Aktualisierungen sehen (Cache-Kohärenz usw.)? Ich möchte im Allgemeinen nicht zu viel darüber nachdenken, aber es ist schön, ein Verständnis zu haben.

BEARBEITEN:

Vielleicht war meine Frage nicht klar, aber ich suche insbesondere nach Gründen, warum die obigen Versuche gut oder schlecht sind. Beachten Sie, dass ich hier von einem Szenario eines einzelnen Autors spreche, der schreibt und dann vor dem gleichzeitigen Lesen einfriert. Ich glaube, Versuch 1 ist in Ordnung, aber ich möchte genau wissen, warum (ich frage mich, ob die Lesevorgänge zum Beispiel irgendwie optimiert werden könnten). Es geht mir weniger darum, ob dies eine gute Entwurfspraxis ist oder nicht, sondern vielmehr um den eigentlichen Threading-Aspekt.

Vielen Dank für die Antwort auf die Frage, aber ich habe mich entschieden, dies als Antwort zu markieren, da ich der Meinung bin, dass die gegebenen Antworten nicht zutreffenziemlich Beantworten Sie meine Frage und ich möchte nicht den Eindruck erwecken, dass die markierte Antwort korrekt ist, nur weil sie aufgrund des Ablaufs der Prämie automatisch als solche markiert wurde. Außerdem glaube ich nicht, dass die Antwort mit der höchsten Stimmenzahl mit überwältigender Mehrheit gewählt wurde, was nicht ausreicht, um sie automatisch als Antwort zu markieren.

Ich bin immer noch geneigt, zu versuchen, Nummer 1 zu sein, aber ich hätte mir einige maßgebliche Antworten gewünscht. Ich verstehe, dass x86 ein starkes Modell hat, aber ich möchte (und sollte nicht) für eine bestimmte Architektur programmieren, schließlich ist dies eines der schönen Dinge an .NET.

Wenn Sie Zweifel an der Antwort haben, wählen Sie einen der Sperransätze, möglicherweise mit den hier gezeigten Optimierungen, um viele Konflikte mit der Sperre zu vermeiden.

Antworten auf die Frage(12)

Ihre Antwort auf die Frage