2010-08-09 13 views
7

Ich bin verwirrt von einem Code-Listing in einem Buch ich lese, C# 3 in Kürze, auf Threading. Im Thema auf Thema Sicherheit in Anwendungsserver, unter Code als Beispiel eines UserCache gegeben:Warum sperren beim Lesen aus einem Wörterbuch

in einem Schloss
static class UserCache 
{ 
    static Dictionary< int,User> _users = new Dictionary< int, User>(); 

    internal static User GetUser(int id) 
    { 
     User u = null; 

     lock (_users) // Why lock this??? 
      if (_users.TryGetValue(id, out u)) 
       return u; 

     u = RetrieveUser(id); //Method to retrieve from databse 

     lock (_users) _users[id] = u; //Why lock this??? 
      return u; 
    } 
} 

Die Autoren erklären, warum die RetrieveUser Methode ist nicht, das ist zum Sperren den Cache zu vermeiden ein längerer Zeitraum.
Ich bin verwirrt, warum sperren Sie den TryGetValue und das Update des Wörterbuchs, denn auch mit dem oben genannten wird das Wörterbuch zweimal aktualisiert, wenn 2 Threads gleichzeitig mit der gleichen nicht abgerufenen ID aufrufen.

Was wird erreicht, wenn das Wörterbuch gelesen wird?
Vielen Dank im Voraus für alle Ihre Kommentare und Einblicke.

Antwort

14

Die Dictionary<TKey, TValue> Klasse is not 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. (Wenn zum Beispiel der Schreibvorgang eine Größenänderung des Arrays auslöst oder wenn die beiden Schlüssel eine Hash-Kollision sind)

Daher verwendet der Code eine Sperre, um gleichzeitige Schreibvorgänge zu verhindern.

+0

Danke! Das macht Sinn. Ich denke, es gibt wirklich keine Möglichkeit, es deterministisch zu beweisen. Ich habe versucht, die Sperren zu entfernen und habe es einige tausend Mal in einer Schleife aufgerufen, indem ich neue Threads erstellt habe, nachdem ich den Code so geändert habe, dass jeder Thread nach dem Lesen immer schreibt, aber es hat gut funktioniert. Aber wie bei den meisten Threads, bedeutet es nicht, dass es immer funktioniert. Prost! – eastender

+0

Muss ich es noch sperren, wenn Dictionary einmal mit einem einzelnen Thread gesetzt ist und danach nur von mehreren Threads gelesen wird? Wahrscheinlich nicht aber eine andere Meinung wird nützlich sein – Adassko

+0

Nein; mehrere Lesevorgänge sind in Ordnung. – SLaks

1

, dass eine gemeinsame Praxis ist jeden nicht-Thread für den Zugriff auf sichere Strukturen wie Listen, Wörterbücher, gemeinsame Werte usw.

Und Haupt Frage zu beantworten: eine Lesesperr wir garantieren, dass Wörterbuch nicht von einer anderen Thread, während geändert werden Wir lesen seinen Wert. Dies ist nicht im Wörterbuch implementiert und deshalb heißt es nicht threadsicher :)

4

Es gibt eine gute Race-Bedingung, wenn Schreiben zum Wörterbuch; 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 gesammelt, wenn der erste Thread damit fertig ist.

Die lesen in das Wörterbuch muss gesperrt werden, weil ein anderer Thread möglicherweise gleichzeitig schreiben, und das Lesen muss über eine konsistente Struktur suchen.

Beachten Sie, dass die in .NET 4.0 eingeführte ConcurrentDictionary diese Art von Idiom ziemlich ersetzt.

0

Wenn zwei Threads gleichzeitig aufrufen und die ID existiert, geben beide die korrekten Benutzerinformationen zurück. 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 niemals 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, der Author verglichen zwei Szenarien - das obige Szenario, das extrem selten sein wird und blockieren, während zwei Threads versuchen und die gleichen Daten schreiben, und zweitens, wo es viel wahrscheinlicher ist, dass zwei Threads fordern Daten anfordern für 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, so dass der Autor für die Ausführung nicht den gesamten Block sperren wollte.

Verwandte Themen