Ich bin verwirrt von einem Code-Listing in einem Buch, das ich gerade lese, C # 3 in Kürze, auf Threading. Im Thema Threadsicherheit in Anwendungsservern wird der folgende Code als Beispiel für einen UserCache angegeben:
%Vor% Die Autoren erklären, warum die RetrieveUser-Methode nicht in einer Sperre ist, um den Cache für einen längeren Zeitraum nicht zu sperren.
Ich bin verwirrt, warum die TryGetValue und das Update des Wörterbuchs sperren, da auch mit dem oben genannten das Wörterbuch zweimal aktualisiert wird, wenn 2 Threads gleichzeitig mit der gleichen nicht abgerufenen ID aufrufen.
Was erreicht wird, wenn das Wörterbuch gelesen wird?
Vielen Dank im Voraus für Ihre Kommentare und Einsichten.
Die Klasse Dictionary<TKey, TValue>
ist kein threadsafe .
Wenn ein Thread einen Schlüssel in das Wörterbuch schreibt, während ein anderer Thread das Wörterbuch liest, kann es zu Problemen kommen. (Zum Beispiel, wenn die Schreiboperation eine Array-Größenänderung auslöst oder wenn die beiden Schlüssel eine Hash-Kollision sind)
Daher verwendet der Code eine Sperre, um gleichzeitige Schreibvorgänge zu verhindern.
Es gibt eine gute Race Condition, wenn in das Wörterbuch geschrieben wird ; Es ist möglich, wie Sie angegeben haben, dass zwei Threads feststellen, dass im Cache kein übereinstimmender Eintrag vorhanden ist. In diesem Fall lesen beide von der DB und versuchen dann einzufügen. Nur das vom letzten Thread eingefügte Objekt wird beibehalten; Das andere Objekt wird als Garbage Collection erfasst, wenn der erste Thread damit erledigt ist.
Das Lesen im Wörterbuch muss gesperrt werden, da ein anderer Thread möglicherweise gleichzeitig schreibt und der Lesevorgang über eine konsistente Struktur erfolgen muss.
Beachten Sie, dass das in .NET 4.0 eingeführte ConcurrentDictionary
diese Art von Idiom ziemlich ersetzt.
Das ist eine gängige Praxis, um auf nicht-thread-sichere Strukturen wie Listen, Wörterbücher, gemeinsame Werte usw. zuzugreifen.
Und zur Beantwortung der Hauptfrage: Sperren eines Lesevorgangs Wir garantieren, dass das Wörterbuch nicht von einem anderen Thread geändert wird, während wir seinen Wert lesen. Dies ist nicht im Dictionary implementiert und deshalb heißt es nicht threadsicher:)
Wenn zwei Threads gleichzeitig anrufen und die ID existiert, werden beide die korrekten Benutzerinformationen zurückgeben. Die erste Sperre soll Fehler verhindern, wie SLaks sagte - wenn jemand in das Wörterbuch schreibt, während Sie versuchen, es zu lesen, werden Sie Probleme haben. In diesem Szenario wird die zweite Sperre nie erreicht.
Wenn zwei Threads gleichzeitig aufrufen und die ID nicht existiert, wird ein Thread gesperrt und TryGetValue eingeben, dies wird false zurückgeben und u auf einen Standardwert setzen. Diese erste Sperre ist erneut, um die von SLaks beschriebenen Fehler zu vermeiden. An diesem Punkt wird der erste Thread die Sperre freigeben und der zweite Thread wird eintreten und dasselbe tun. Beide setzen dann 'u' auf Informationen von 'RetrieveUser (id)'; Dies sollte die gleiche Information sein. Ein Thread wird dann das Wörterbuch sperren und _users [id] dem Wert von u zuweisen. Diese zweite Sperre ist so, dass zwei Threads versuchen, Werte an denselben Speicherorten gleichzeitig zu schreiben und diesen Speicher zu beschädigen. Ich weiß nicht, was der zweite Thread tun wird, wenn er den Auftrag eingibt. Entweder wird das Update entweder zu früh zurückgegeben, oder es werden die vorhandenen Daten aus dem ersten Thread überschrieben. Unabhängig davon enthält das Dictionary dieselben Informationen, da beide Threads die gleichen Daten in "u" von RetrieveUser erhalten haben sollten.
Für die Leistung hat der Autor zwei Szenarien verglichen - das obige Szenario, das extrem selten sein wird und blockiert, während zwei Threads versuchen und dieselben Daten schreiben, und das zweite, bei dem es viel wahrscheinlicher ist, dass zwei Threads anfragende Daten anfordern ein Objekt, das geschrieben werden muss, und eines, das existiert. Zum Beispiel rufen threadA und threadB gleichzeitig auf und ThreadA sperrt für eine ID, die nicht existiert. Es gibt keinen Grund dafür, dass threadB auf eine Suche wartet, während threadA mit RetriveUser arbeitet. Diese Situation ist wahrscheinlich viel wahrscheinlicher als die oben beschriebenen Duplikat-IDs. Daher entschied sich der Autor für die Leistung, den gesamten Block nicht zu sperren.
Tags und Links c# multithreading caching dictionary locking