2010-08-19 3 views
8

Mögliche Duplizieren:
Are C# auto-implemented static properties thread-safe?Ist ein einfacher Thread für statische Eigenschaften sicher und sicher?

Im folgenden Beispiel Klasse

static class Shared 
{ 
    public static string[] Values { get; set; } 
} 

viele Leser Threads gelesen das Values String-Array periodisch, während von Zeit zu Zeit ein einzelner writer ersetzt das gesamte Array durch einen neuen Wert mit dem Setter. Muss ich eine ReaderWriterLock verwenden, oder wird dies von C# automatisch gehandhabt?

Edit: In meinem Fall ist die einzige erforderliche "Thread-Sicherheit": Nichts Schlechtes sollte passieren, wenn der Schreiber das Array ersetzt, während der Leser nach einem Wert sucht. Es ist mir egal, ob die Leser den neuen Wert sofort verwenden werden, solange sie ihn in Zukunft verwenden.

+2

Doppelte von http://stackoverflow.com/questions/2074670/are-c-auto-implemented-static-properties-thread-safe? –

+0

Überprüfen Sie den Namespace 'System.Collections.Concurrent', wenn Sie in .NET 4 sind. Abhängig von Ihrer Verwendung können sie auch Ihren Anforderungen entsprechen. – Marc

Antwort

10

Diese Verwendung ist Thread-sicher:

string[] localValues = Shared.Values; 
for (int index = 0; index < localValues.length; index++) 
    ProcessValues(localValues[index]); 

Diese Verwendung ist nicht Thread-sicher, und in Out-of-bounds Ausnahmen führen kann:

for (int index = 0; index < Shared.Values.Length; index++) 
    ProcessValues(Shared.Values[index]); 

Ich würde es vorziehen zu machen thread safe ruft natürlicher auf, indem man so etwas tut:

static class Shared 
{ 
    private static string[] values; 
    public static string[] GetValues() { return values; } 
    public static void SetValues(string[] values) { Shared.values = values; } 
} 

Natürlich können die Benutzer GetValues ​​() in die Schleifen setzen, und es wäre genauso schlimm, aber zumindest ist es offensichtlich schlecht.

Je nach Situation kann eine noch bessere Lösung darin bestehen, Kopien zu verteilen, damit der aufrufende Code das Array nicht mutieren kann. Dies ist, was ich normalerweise tun würde, aber es kann nicht in Ihrer Situation angemessen sein.

static class Shared 
{ 
    private static string[] values; 
    public static string[] GetValues() 
    { 
     string[] currentValues = values; 
     if (currentValues != null) 
      return (string[])currentValues.Clone(); 
     else 
      return null; 
    } 
    public static void SetValues(string[] values) 
    { 
     Shared.values = values; 
    } 
} 
+2

Um diese Antwort hinzuzufügen: Eine Sperre ist in diesem Lazy-Read-Szenario nicht erforderlich, da die Verwendung eines Verweises auf das gemeinsam genutzte Array als atomare Operation dient.Das Lesen eines Verweises (Zeigers) von einer gemeinsamen Variablen ist threadsicher, selbst wenn ein anderer Thread gerade einen neuen Wert in die gemeinsam genutzte Variable schreibt. Dies ist eine Form der "generationalen" Versionierung - jedes Mal, wenn Sie einen Verweis auf die geteilten Daten nehmen, ist es im Wesentlichen ein unveränderlicher Schnappschuss der Daten, der solange am Leben gehalten wird, bis alle Verweise darauf freigegeben sind. – dthorpe

+0

Klarstellung: Lesen der Referenzzeiger sind atomar auf der Hardware-Ebene auf x86 und x64, wenn die Speicheradresse dword-ausgerichtet ist, was ich ziemlich sicher durch den .NET-Zuweiser garantiert wird. Nicht-ausgerichtete Lesevorgänge können mehrere Taktzyklen benötigen, um jede Hälfte zu lesen und sie zu kombinieren, und dies schafft ein Gelegenheitsfenster für einen Schreibvorgang in einem anderen Thread, um den Wert im Speicher zu ändern, bevor der Lesevorgang die zweite Hälfte gelesen hat. – dthorpe

+3

@dthorpe - Wichtig ist, dass die C# -Sprachdefinition garantiert, dass ein Verweis auf ein Objekt in einer atomaren Fasion kopiert wird, unabhängig von der Plattform, auf der es ausgeführt wird oder wie der Mechanismus implementiert ist. Und das ist eine ziemlich nützliche Sache für solche Umstände. :-) –

2

Dies ist nicht threadsicher. Ja, Sie müssen ein Schloss verwenden.

+1

Diese Antwort wäre viel nützlicher (oder falsifizierbar), wenn sie einige Details enthält - wie die Gefahren, die Sie vorhersehen. –

+0

"Thread-safe" und "nicht thread-safe" werden zu lose verwendet. In diesem Fall sind Lese- und Schreibvorgänge garantiert atomar, aber möglicherweise nicht in der richtigen Reihenfolge. Eine Sperre behält die Reihenfolge bei, aber die Reihenfolge ist hier möglicherweise nicht wichtig. –

5

Es hängt davon ab, was Sie unter Thread-Safe verstehen.

Das Lesen und Ersetzen ist garantiert atomar, aber es ist nicht garantiert, dass ein Lesen nach einem Schreibvorgang notwendigerweise den neuen Wert liest.

Als Antwort auf Ihre bearbeiten ...

Nichts Schlimmes wird mit Ihrem vorhandenen Code geschehen (zum Beispiel liest zerrissen), aber es gibt keine Garantie, dass Ihre Leser jemals den neuen Wert zu sehen . Zum Beispiel ist es möglich - wenn auch vielleicht unwahrscheinlich -, dass die alte Referenz für immer in einem Register zwischengespeichert wird.

+0

Ich habe meine Frage mit dem erforderlichen Grad an Gewindesicherheit aktualisiert. – xsl

7

Ohne zusätzlichen Schutz gibt es keine Garantie, dass ein Lesen Thread jemals den neuen Wert sehen wird. In der Praxis wird den neuen Wert sehen, solange der Lese-Thread etwas Bedeutendes tut. Insbesondere werden Sie niemals "halb" ein Update sehen, mit einem Verweis auf Totraum.

Wenn Sie das Feld volatile machen, glaube ich, dass auch diese Gefahr entfernt wird - aber Lock-Free-Programmierung ist in der Regel schwer zu begründen. Dies bedeutet natürlich, dass es aufgegeben wird, eine automatisch implementierte Eigenschaft zu sein.

+0

@LukeH: Möglicherweise ... mein Verständnis ist auch begrenzt. Ich glaube jedoch, dass es "mehr oder weniger" dafür sorgt, dass Sie immer den letzten Wert lesen. (Das ist sogar die MSDN-Beschreibung davon.) –

1

Ich würde sperren. Verwenden Sie für mehrfaches Lesen, gelegentliches Schreiben eine ReaderWriterLockSlim - viel effizienter als ReaderWriteLock.

static class Shared 
{ 
    private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); 
    private static string[] _values; 

    public static string[] Values 
    { 
     get 
     { 
      _rwLock.EnterReadLock(); 
      try 
      { 
       return _values; 
      } 
      finally 
      { 
       _rwLock.ExitReadLock(); 
      } 
     } 
     set 
     { 
      _rwLock.EnterWriteLock(); 
      try 
      { 
       _values = value; 
      } 
      finally 
      { 
       _rwLock.ExitWriteLock(); 
      } 
     } 
    } 
} 
+0

Ein einfaches altes 'Schloss' wäre noch schneller. Der Grund ist, dass RW-Sperren eine begrenzte Anzahl von Szenarien haben, in denen sie tatsächlich in der Praxis schneller sind. Dies ist keiner von ihnen. Ich habe gerade einen Benchmark auf meiner Maschine gemacht und ein "Lock" war etwa 5x schneller. –

+0

Ich stimme Brian zu, eine Sperre ist schneller, aber wenn wir über eine große Anzahl von Threads sprechen, die auf die Daten zugreifen, dann hat das vielleicht einige Vorteile. In menschlichen Begriffen ist der Unterschied jedoch ziemlich unbedeutend! – Michael

1

Um etwas off-topic zu gehen, fügt der Java 2 1.5 Speichermodell zwei Garantien (siehe Java theory and practice: Fixing the Java Memory Model, Part 2):

  • flüchtig liest/schreibt auch Barrieren Speicher sind.
  • final Felder erscheinen außerhalb des Konstruktors vollständig initialisiert.

Letzteres ist ein interessanter Fall: Sie verwendet der Lage sein, die leere Zeichenfolge zu tun foo = new String(new char[]{'a','b','c'}); in einem Thread und foo = "123"; System.out.println(foo) in einem anderen Thread und drucken, weil es keine Garantie dafür, dass die Schreibvorgänge auf foo endgültigen Felder passieren wird, bevor das Schreiben an foo.

Ich bin mir nicht sicher über die Details der .NET-Array-Initialisierung; Möglicherweise müssen Sie Thread.MemoryBarrier() (am Anfang des Getters und am Ende des Setter) verwenden, um sicherzustellen, dass die Leser nur vollständig initialisierte Arrays sehen.