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?
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.
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.
Sehen Sie sich diese Anleitung zum Sperren an, es ist gut
Ein paar zu beachtende Richtlinien
lock(this)
, was zu Deadlocks führen kann! 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.
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.
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%Tags und Links c# multithreading