C # Ist diese Methode sicher?

8

Betrachten Sie den folgenden Code:

%Vor%

Angenommen ich rufe MyMethod("mystring") von verschiedenen Threads gleichzeitig auf.

Wäre es möglich, dass mehr als ein Thread (wir nehmen es nur als zwei) die if (!list.Contains(a)) -Anweisung gleichzeitig eingeben (mit ein paar CPU-Zyklusunterschieden), werden beide Threads als false ausgewertet und ein Thread tritt in die kritische Region ein, während eine andere außerhalb gesperrt wird, so dass der zweite Thread eintritt und "mystring" erneut zur Liste hinzufügt, nachdem der erste Thread beendet wurde, was dazu führt, dass das Wörterbuch versucht, einen doppelten Schlüssel hinzuzufügen?

    
user1928346 24.10.2013, 13:53
quelle

7 Antworten

18

Nein, es ist nicht threadsicher. Sie benötigen die Sperre auch um list.Contains , da es möglich ist, dass zwischen dem if-Test und dem Hinzufügen der Daten ein Thread aus- und wieder eingefügt wird. Ein anderer Thread kann zwischenzeitlich Daten hinzugefügt haben.

    
David Arno 24.10.2013, 13:55
quelle
11

Sie müssen die gesamte Operation sperren (überprüfen und hinzufügen) oder mehrere Threads versuchen, den gleichen Wert hinzuzufügen.

Ich würde empfehlen, das ConcurrentDictionary(TKey, TValue) zu verwenden, da es Thread-sicher ist.

%Vor%     
Romoku 24.10.2013 14:05
quelle
1

Sie müssen die gesamte Anweisung sperren. Es ist möglich, dass Sie Probleme mit dem .Contains -Anteil haben (so wie Ihr Code jetzt ist)

    
gleng 24.10.2013 13:55
quelle
1

Sie sollten die Liste nach dem Sperren überprüfen. z.B.

%Vor%     
basher 24.10.2013 13:55
quelle
1
%Vor%

Sehen Sie sich diese Anleitung zum Sperren an, es ist gut

Ein paar zu beachtende Richtlinien

  1. Sperren Sie im Allgemeinen ein privates statisches Objekt, wenn Sie mehrere schreibbare Werte sperren
  2. Sperren Sie keine Objekte mit einem Bereich außerhalb der Klasse oder der lokalen Methode wie lock(this) , was zu Deadlocks führen kann!
  3. Sie können das Objekt, das geändert wird, sperren, wenn es das einzige Objekt ist, auf das gleichzeitig zugegriffen wird
  4. Stellen Sie sicher, dass das von Ihnen gesperrte Objekt nicht null ist!
  5. Sie können nur Referenztypen sperren
Jason Carney 24.10.2013 14:01
quelle
0

Ich gehe davon aus, dass Sie ContainsKey anstelle von Contains schreiben wollten. Contains auf einem Dictionary ist explizit implementiert, so dass es nicht über den von Ihnen angegebenen Typ zugänglich ist. 1

Ihr Code ist nicht sicher. Der Grund dafür ist, dass nichts verhindert, dass ContainsKey und Add gleichzeitig ausgeführt werden. Es gibt tatsächlich einige ziemlich bemerkenswerte Fehlerszenarien, die dies einführen würde. Da ich gesehen habe, wie Dictionary implementiert ist, kann ich sehen, dass Ihr Code eine Situation verursachen könnte, in der die Datenstruktur Duplikate enthält. Und ich meine, es enthält wörtlich Duplikate. Die Ausnahme wird nicht unbedingt ausgelöst. Die anderen Fehlerszenarien werden immer seltsamer und seltsamer, aber ich werde hier nicht näher darauf eingehen.

Eine geringfügige Änderung an Ihrem Code kann eine Variation des doppelt überprüften Sperrmusters beinhalten.

%Vor%

Dies ist natürlich nicht sicherer aus dem Grund, den ich bereits erwähnt habe. Tatsächlich ist es schwierig, das doppelt überprüfte Sperrmuster in allen, außer den einfachsten Fällen (wie die kanonische Implementierung eines Singleton), zu korrigieren. Zu diesem Thema gibt es viele Variationen. Sie können es mit TryGetValue oder dem Standard-Indexer versuchen, aber letztlich sind alle diese Variationen einfach falsch.

Also wie könnte das richtig gemacht werden, ohne ein Schloss zu nehmen? Sie könnten ConcurrentDictionary ausprobieren. Es hat die Methode GetOrAdd , die in diesen Szenarien wirklich nützlich ist. Ihr Code würde so aussehen.

%Vor%

Das ist alles, was dazu gehört. Die Funktion GetOrAdd überprüft, ob das Element vorhanden ist. Wenn nicht, wird es hinzugefügt. Andernfalls wird die Datenstruktur allein gelassen. Dies alles geschieht auf thread-sichere Weise. In den meisten Fällen macht das ConcurrentDictionary dies, ohne auf eine Sperre zu warten. 2

1 Übrigens, Ihr variabler Name ist auch anstößig. Ohne Servys Kommentar hätte ich vielleicht übersehen, dass wir von einem Dictionary im Gegensatz zu einem List sprechen. In der Tat, basierend auf dem Aufruf von Contains dachte ich zuerst, wir sprachen über ein List .

2 Auf den ConcurrentDictionary -Lesern sind alle frei von Sperren. Writer always nehmen jedoch eine Sperre (fügt hinzu und aktualisiert das, die remove-Operation ist immer noch sperrfrei). Dies beinhaltet die Funktion GetOrAdd . Der Unterschied besteht darin, dass die Datenstruktur mehrere mögliche Sperroptionen enthält, sodass in den meisten Fällen keine oder nur geringe Sperrkonflikte auftreten. Aus diesem Grund wird diese Datenstruktur als "Low Lock" oder "Concurrent" im Gegensatz zu "Lock Free" bezeichnet.

    
Brian Gideon 24.10.2013 18:11
quelle
-2

Sie können zuerst eine nicht-verriegelnde Prüfung durchführen, aber wenn Sie threadsicher sein möchten, müssen Sie die Prüfung innerhalb des Schlosses erneut durchführen. Auf diese Weise sperren Sie nicht, es sei denn, Sie müssen die Threadsicherheit sicherstellen und sicherstellen.

%Vor%     
Lefteris E 24.10.2013 14:05
quelle

Tags und Links