Ich habe die folgende Klasse.
class Test{
public HashSet<string> Data = new HashSet<string>();
}
Ich muss das Feld "Daten" aus verschiedenen Threads ändern, daher möchte ich einige Meinungen zu meiner aktuellen thread-sicheren Implementierung haben.
class Test{
public HashSet<string> Data = new HashSet<string>();
public void Add(string Val){
lock(Data) Data.Add(Val);
}
public void Remove(string Val){
lock(Data) Data.Remove(Val);
}
}
Gibt es eine bessere Lösung, um direkt zum Feld zu gelangen und es vor dem gleichzeitigen Zugriff durch mehrere Threads zu schützen?
System.Collections.Concurrent
ReaderWriterLock
ist hilfreich (effizient), wenn mehrere Leser und ein einzelner Schreiber vorhanden sind. Wir müssen wissen, ob dies bei OP der Fall istAntworten:
Ihre Implementierung ist korrekt. Das .NET Framework bietet leider keinen integrierten gleichzeitigen Hashset-Typ. Es gibt jedoch einige Problemumgehungen.
ConcurrentDictionary (empfohlen)
Diese erste besteht darin, die Klasse
ConcurrentDictionary<TKey, TValue>
im Namespace zu verwendenSystem.Collections.Concurrent
. In diesem Fall ist der Wert sinnlos, sodass wir ein einfachesbyte
(1 Byte im Speicher) verwenden können.Dies ist die empfohlene Option, da der Typ threadsicher ist und Ihnen dieselben Vorteile bietet, als
HashSet<T>
wenn ein Ausnahmeschlüssel und ein Wert unterschiedliche Objekte sind.Quelle: Soziale MSDN
ConcurrentBag
Wenn Ihnen die doppelten Einträge nichts ausmachen, können Sie die Klasse
ConcurrentBag<T>
im selben Namespace wie die vorherige Klasse verwenden.Selbstimplementierung
Schließlich können Sie wie Sie Ihren eigenen Datentyp implementieren, indem Sie Sperren oder andere Methoden verwenden, die .NET Ihnen bietet, um threadsicher zu sein. Hier ist ein gutes Beispiel: So implementieren Sie ConcurrentHashSet in .Net
Der einzige Nachteil dieser Lösung besteht darin, dass der Typ
HashSet<T>
selbst für Lesevorgänge keinen offiziell gleichzeitigen Zugriff bietet.Ich zitiere den Code des verlinkten Beitrags (ursprünglich von Ben Mosher geschrieben ).
BEARBEITEN: Verschieben Sie die Eingangsverriegelungsmethoden außerhalb der
try
Blöcke, da sie eine Ausnahme auslösen und die in denfinally
Blöcken enthaltenen Anweisungen ausführen können .quelle
null
Referenz handelt (die Referenz benötigt 4 Byte in einer 32-Bit-Laufzeit und 8 Byte in einer 64-Bit-Laufzeit). Daher kann die Verwendung von abyte
, einer leeren Struktur oder ähnlichem den Speicherbedarf verringern (oder nicht, wenn die Laufzeit die Daten an nativen Speichergrenzen ausrichtet, um einen schnelleren Zugriff zu ermöglichen).Anstatt ein zu verpacken
ConcurrentDictionary
oder ein zu sperren, habeHashSet
ich ein aktuellesConcurrentHashSet
basierend auf erstelltConcurrentDictionary
.Diese Implementierung unterstützt grundlegende Operationen pro Element ohne
HashSet
festgelegte Operationen, da sie in gleichzeitigen Szenarien weniger sinnvoll sind. IMO:Ausgabe: 2
Sie können es von NuGet hier bekommen und die Quelle auf GitHub hier sehen .
quelle
ISet<T>
Schnittstelle bo tatsächlich derHashSet<T>
Semantik entspricht?Overlaps
Beispielsweise müsste entweder die Instanz während des gesamten Laufs gesperrt werden oder eine Antwort bereitgestellt werden, die möglicherweise bereits falsch ist. Beide Optionen sind schlechte IMO (und können von Verbrauchern extern hinzugefügt werden).Da es niemand anderes erwähnt hat, werde ich einen alternativen Ansatz anbieten, der für Ihren speziellen Zweck geeignet sein kann oder nicht:
Unveränderliche Microsoft-Sammlungen
Aus einem Blogbeitrag des dahinter stehenden MS-Teams:
Diese Sammlungen umfassen ImmutableHashSet <T> und ImmutableList <T> .
Performance
Da die unveränderlichen Sammlungen darunter liegende Baumdatenstrukturen verwenden, um die gemeinsame Nutzung von Strukturen zu ermöglichen, unterscheiden sich ihre Leistungsmerkmale von veränderlichen Sammlungen. Beim Vergleich mit einer veränderlichen Sammlung von Sperren hängen die Ergebnisse von Sperrenkonflikten und Zugriffsmustern ab. Aus einem anderen Blog-Beitrag über die unveränderlichen Sammlungen:
Mit anderen Worten, in vielen Fällen wird der Unterschied nicht spürbar und Sie sollten sich für die einfachere Wahl entscheiden - die für gleichzeitige Sets verwendet werden sollte
ImmutableHashSet<T>
, da Sie keine veränderbare veränderbare Locking-Implementierung haben! :-)quelle
ImmutableHashSet<T>
hilft nicht viel, wenn Sie beabsichtigen, den freigegebenen Status von mehreren Threads zu aktualisieren, oder fehlt mir hier etwas?ImmutableInterlocked.Update
scheint das fehlende Glied zu sein. Danke dir!Der schwierige Teil beim
ISet<T>
gleichzeitigen Erstellen besteht darin, dass die festgelegten Methoden (Vereinigung, Schnittmenge, Differenz) iterativer Natur sind. Zumindest müssen Sie alle n Mitglieder eines der an der Operation beteiligten Sätze durchlaufen, während Sie beide Sätze sperren.Sie verlieren die Vorteile von a,
ConcurrentDictionary<T,byte>
wenn Sie den gesamten Satz während der Iteration sperren müssen. Ohne Sperren sind diese Vorgänge nicht threadsicher.Angesichts des zusätzlichen Overheads von
ConcurrentDictionary<T,byte>
ist es wahrscheinlich klüger, nur das geringere Gewicht zu verwendenHashSet<T>
und alles in Schlössern zu umgeben.Wenn Sie die festgelegten Operationen nicht benötigen, verwenden Sie
ConcurrentDictionary<T,byte>
und verwenden Sie sie nurdefault(byte)
als Wert, wenn Sie Schlüssel hinzufügen.quelle
Ich bevorzuge Komplettlösungen, deshalb habe ich Folgendes getan: Wohlgemerkt, meine Zählung wird auf eine andere Weise implementiert, da ich nicht verstehe, warum es verboten sein sollte, das Hashset zu lesen, während versucht wird, seine Werte zu zählen.
@ Zen, Danke, dass du damit angefangen hast.
quelle
EnterWriteLock
, warum gibtEnterReadLock
es überhaupt? Kann es nicht die Lesesperre für Methoden wie verwendet werdenContains
?