2009-12-22 8 views
99

Ich konnte nicht genug Informationen über ConcurrentDictionary Typen finden, also dachte ich, ich würde mich darum erkundigen..NET - Wörterbuchverriegelung vs. ConcurrentDictionary

Derzeit verwende ich eine Dictionary, um alle Benutzer zu halten, die ständig von mehreren Threads (aus einem Thread-Pool, also keine genaue Anzahl von Threads) zugegriffen wird, und es hat Zugriff synchronisiert.

Ich habe kürzlich herausgefunden, dass es eine Reihe von thread-sicheren Sammlungen in .NET 4.0 gab, und es scheint sehr erfreulich zu sein. Ich frage mich, was wäre die "effizienter und einfacher zu verwalten" -Option, wie ich die Option zwischen einem normalen Dictionary mit synchronisiertem Zugriff haben, oder haben Sie eine ConcurrentDictionary, die bereits Thread-sicher ist.

Reference to .NET 4.0's ConcurrentDictionary

+2

Möglicherweise möchten Sie sehen [diesen Code Projekt Artikel zu diesem Thema] (http: //www.codeproject.com/Articles/548406/Dictionary-plus-Locking-versus-ConcurrentDictionar) – nawfal

Antwort

124

Eine Thread-sichere Sammlung im Vergleich zu einer Nicht-Thread-Sammlung kann auf andere Weise betrachtet werden.

Betrachten Sie ein Geschäft ohne Verkäufer, außer an der Kasse. Sie haben eine Menge Probleme, wenn Menschen nicht verantwortungsvoll handeln. Nehmen wir an, ein Kunde nimmt eine Dose aus einer Pyramidendose, während ein Angestellter gerade die Pyramide baut, die Hölle würde sich lösen. Oder, wenn zwei Kunden gleichzeitig nach dem gleichen Gegenstand greifen, wer gewinnt? Wird es einen Kampf geben? Dies ist eine Nicht-Thread-Safe-Sammlung. Es gibt viele Möglichkeiten, Probleme zu vermeiden, aber sie alle erfordern eine Art von Sperren oder vielmehr expliziten Zugriff auf die eine oder andere Weise.

Auf der anderen Seite, betrachten Sie ein Geschäft mit einem Angestellten an einem Schreibtisch, und Sie können nur durch ihn einkaufen. Du reihst dich ein und fragst ihn nach einem Gegenstand, er bringt ihn zurück und du gehst aus der Reihe. Wenn Sie mehrere Gegenstände benötigen, können Sie bei jeder Rundfahrt nur so viele Gegenstände aufnehmen, wie Sie sich erinnern können, aber Sie müssen vorsichtig sein, um den Verkäufer nicht zu nerven, dies wird die anderen Kunden in der Schlange hinter Ihnen verärgern.

Nun betrachten Sie dies. In dem Geschäft mit einem Angestellten, was ist, wenn Sie den ganzen Weg zur Vorderseite der Linie gehen, und fragen Sie den Angestellten "Haben Sie Toilettenpapier", und er sagt "Ja", und dann gehen Sie "Ok, ich" Ich melde mich bei Ihnen, wenn ich weiß, wie viel ich brauche ", und wenn Sie wieder an vorderster Front stehen, kann der Laden natürlich ausverkauft sein. Dieses Szenario wird nicht durch eine threadsafe-Sammlung verhindert.

Eine threadsafe-Sammlung garantiert, dass ihre internen Datenstrukturen immer gültig sind, selbst wenn von mehreren Threads aus zugegriffen wird.

Eine nicht fadensichere Sammlung wird nicht mit solchen Garantien geliefert. Wenn Sie zum Beispiel etwas zu einem Binärbaum in einem Thread hinzufügen, während ein anderer Thread damit beschäftigt ist, den Baum wieder auszugleichen, gibt es keine Garantie dafür, dass das Element hinzugefügt wird, oder sogar, dass der Baum danach noch gültig ist.

Eine THREAD Sammlung jedoch nicht garantieren, dass die aufeinander folgenden Operationen auf dem Thread alle Arbeiten auf dem gleichen „Momentaufnahme“ der internen Datenstruktur, was bedeutet, dass, wenn Sie Code wie folgt:

if (tree.Count > 0) 
    Debug.WriteLine(tree.First().ToString()); 

Sie könnten eine NullReferenceException erhalten, weil zwischen tree.Count und tree.First() ein anderer Thread die verbleibenden Knoten in der Struktur gelöscht hat, was bedeutet, dass First()null zurückgibt.

Für dieses Szenario müssen Sie entweder sehen, ob die fragliche Sammlung einen sicheren Weg hat, um zu bekommen, was Sie wollen, vielleicht müssen Sie den obigen Code neu schreiben, oder Sie müssen möglicherweise sperren.

+7

Jedes Mal wenn ich diesen Artikel lese, obwohl es alles stimmt, gehen wir irgendwie den falschen Weg. –

+18

Das Hauptproblem in einer threadfähigen Anwendung sind veränderbare Datenstrukturen. Wenn Sie das vermeiden können, sparen Sie sich Ärger. –

+0

Offenbar Wörterbücher können einige wirklich böse threading Nebenwirkungen haben: http://stackoverflow.com/questions/14838032/asp-net-hang-generic-dictionary-concurrency-issues-causes-gc-deadlock – jocull

5

Grundsätzlich möchte man mit dem neuen ConcurrentDictionary gehen. Direkt aus der Box müssen Sie weniger Code schreiben, um thread-sichere Programme zu erstellen.

+0

Gibt es ein Problem, wenn ich Concurrent Dictionery weiterführe? – kbvishnu

53

Sie müssen immer noch sehr vorsichtig sein, wenn Sie Thread-sichere Sammlungen verwenden, da thread-safe nicht bedeutet, dass Sie alle Threading-Probleme ignorieren können. Wenn sich eine Sammlung selbst als Thread-sicher ankündigt, bedeutet dies normalerweise, dass sie in einem konsistenten Zustand bleibt, selbst wenn mehrere Threads gleichzeitig lesen und schreiben. Das bedeutet jedoch nicht, dass ein einzelner Thread eine "logische" Folge von Ergebnissen sehen wird, wenn er mehrere Methoden aufruft.

Wenn Sie beispielsweise zuerst prüfen, ob ein Schlüssel vorhanden ist, und später den Wert abrufen, der dem Schlüssel entspricht, ist dieser Schlüssel möglicherweise nicht mehr vorhanden, auch nicht mit einer ConcurrentDictionary-Version (weil ein anderer Thread den Schlüssel hätte entfernen können). In diesem Fall müssen Sie weiterhin Sperren verwenden (oder besser: kombinieren Sie die beiden Aufrufe mit TryGetValue).

Also verwenden Sie sie, aber denken Sie nicht, dass es Ihnen eine Freikarte gibt, alle Nebenläufigkeitsprobleme zu ignorieren. Du musst immer noch vorsichtig sein.

+4

Also im Grunde sagen Sie, wenn Sie das Objekt nicht korrekt verwenden, wird es nicht funktionieren? – ChaosPandion

+0

Warum ist das? Die Sammlung verliert irgendwie die Veränderung? –

+1

Grundsätzlich müssen Sie TryAdd anstelle der allgemeinen enthält -> hinzufügen. – ChaosPandion

12

Ich denke, dass ConcurrentDictionary.GetOrAdd genau das ist, was die meisten Multithread-Szenarien benötigen.

+1

Ich würde TryGet auch verwenden, sonst verschwenden Sie den Aufwand beim Initialisieren des zweiten Parameters auf GetOrAdd jedes Mal, wenn der Schlüssel bereits existiert. –

+5

* GetOrAdd * hat die Überladung * GetOrAdd (TKey, Func ) *, so dass der zweite Parameter nur dann lazy-initialisiert werden kann, wenn der Schlüssel nicht im Wörterbuch vorhanden ist. Außerdem wird * TryGetValue * zum Abrufen von Daten verwendet, nicht zum Ändern. Nachfolgende Aufrufe an jede dieser Methoden können dazu führen, dass ein anderer Thread den Schlüssel zwischen den beiden Aufrufen hinzugefügt oder entfernt hat. – sgnsajgon

+0

Dies sollte die markierte Antwort auf die Frage sein, obwohl Lasses Beitrag ausgezeichnet ist. – Spivonious

13

Haben Sie die Reactive Extensions für .Net 3.5sp1 gesehen. Laut Jon Skeet haben sie ein Bündel der parallelen Erweiterungen und parallelen Datenstrukturen für .Net3.5 sp1 zurückportiert.

Es gibt eine Reihe von Beispielen für .NET 4 Beta 2, die in ziemlich guten Details beschreibt, wie sie die parallelen Erweiterungen verwenden.

Ich habe gerade die letzte Woche das ConcurrentDictionary mit 32 Threads testen, um I/O durchzuführen. Es scheint so zu funktionieren, wie es angekündigt wurde, was darauf schließen lässt, dass eine enorme Menge an Tests durchgeführt wurde.

Bearbeiten: .NET 4 ConcurrentDictionary und Muster.

Microsoft haben ein PDF namens Patterns of Paralell Programming veröffentlicht. Es lohnt sich wirklich herunterzuladen, da es in sehr schönen Details die richtigen Muster für .Net 4 Concurrent Extensions und die Anti-Patterns zu vermeiden beschreibt. Here it is.

27

Intern ConcurrentDictionary verwendet eine separate Sperre für jeden Hash-Bucket. Solange Sie nur Add/TryGetValue und ähnliche Methoden verwenden, die an einzelnen Einträgen arbeiten, arbeitet das Wörterbuch als eine fast lock-free Datenstruktur mit dem jeweiligen süßen Leistungsvorteil. OTOH Die Aufzählungsmethoden (einschließlich der Count-Eigenschaft) sperren alle Buckets auf einmal und sind daher leistungsmäßig schlechter als ein synchronisiertes Dictionary.

Ich würde sagen, einfach ConcurrentDictionary verwenden.

2

Wir haben ConcurrentDictionary für die zwischengespeicherte Sammlung verwendet, die alle 1 Stunde neu ausgefüllt und dann von mehreren Client-Threads gelesen wird, ähnlich to the solution für die Is this example thread safe? Frage.

Wir fanden, dass die Änderung auf ReadOnlyDictionary verbesserte Gesamtleistung.

+2

Ich weiß den Grund von Downvote zu schätzen? –

+0

ReadOnlyDictionary ist nur der Wrapper um ein anderes IDictionary. Was benutzt du unter der Haube? – Lu55