Ich konnte ein thread-sicheres Wörterbuch in C # implementieren, indem ich von IDictionary abgeleitet und ein privates SyncRoot-Objekt definiert habe:
public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
private readonly object syncRoot = new object();
private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
public object SyncRoot
{
get { return syncRoot; }
}
public void Add(TKey key, TValue value)
{
lock (syncRoot)
{
d.Add(key, value);
}
}
// more IDictionary members...
}
Ich sperre dann dieses SyncRoot-Objekt in meinen Verbrauchern (mehrere Threads):
Beispiel:
lock (m_MySharedDictionary.SyncRoot)
{
m_MySharedDictionary.Add(...);
}
Ich konnte es zum Laufen bringen, aber dies führte zu hässlichem Code. Meine Frage ist, gibt es eine bessere und elegantere Möglichkeit, ein thread-sicheres Wörterbuch zu implementieren?
Antworten:
Wie Peter sagte, können Sie die gesamte Thread-Sicherheit innerhalb der Klasse kapseln. Sie müssen bei allen Ereignissen, die Sie verfügbar machen oder hinzufügen, vorsichtig sein und sicherstellen, dass sie außerhalb von Sperren aufgerufen werden.
Bearbeiten: In den MSDN-Dokumenten wird darauf hingewiesen, dass die Aufzählung von Natur aus nicht threadsicher ist. Dies kann ein Grund dafür sein, ein Synchronisationsobjekt außerhalb Ihrer Klasse verfügbar zu machen. Eine andere Möglichkeit, dies zu erreichen, besteht darin, einige Methoden zum Ausführen einer Aktion für alle Mitglieder bereitzustellen und die Aufzählung der Mitglieder zu überprüfen. Das Problem dabei ist, dass Sie nicht wissen, ob die an diese Funktion übergebene Aktion ein Mitglied Ihres Wörterbuchs aufruft (dies würde zu einem Deadlock führen). Durch das Offenlegen des Synchronisationsobjekts kann der Verbraucher diese Entscheidungen treffen und den Deadlock in Ihrer Klasse nicht verbergen.
quelle
Die .NET 4.0-Klasse, die Parallelität unterstützt, wird benannt
ConcurrentDictionary
.quelle
Der Versuch, intern zu synchronisieren, wird mit ziemlicher Sicherheit unzureichend sein, da die Abstraktionsebene zu niedrig ist. Angenommen, Sie machen die Operationen
Add
undContainsKey
wie folgt einzeln threadsicher:Was passiert dann, wenn Sie dieses vermeintlich threadsichere Codebit aus mehreren Threads aufrufen? Wird es immer gut funktionieren?
Die einfache Antwort lautet nein. Irgendwann löst die
Add
Methode eine Ausnahme aus, die angibt, dass der Schlüssel bereits im Wörterbuch vorhanden ist. Wie kann das mit einem thread-sicheren Wörterbuch sein, könnte man fragen? Nur weil jede Operation threadsicher ist, ist die Kombination von zwei Operationen nicht möglich, da ein anderer Thread sie zwischen Ihrem Aufruf vonContainsKey
und ändern könnteAdd
.Um diese Art von Szenario korrekt zu schreiben, benötigen Sie eine Sperre außerhalb des Wörterbuchs, z
Da Sie jedoch extern sperrenden Code schreiben müssen, verwechseln Sie die interne und externe Synchronisation, was immer zu Problemen wie unklarem Code und Deadlocks führt. Letztendlich sind Sie wahrscheinlich entweder besser:
Verwenden Sie eine normale
Dictionary<TKey, TValue>
und synchronisieren Sie extern, indem Sie die zusammengesetzten Operationen darauf einschließen, oderSchreiben Sie einen neuen thread-sicheren Wrapper mit einer anderen Schnittstelle (dh nicht
IDictionary<T>
), die die Operationen wie eineAddIfNotContained
Methode kombiniert , sodass Sie niemals Operationen daraus kombinieren müssen.(Ich neige dazu, selbst mit # 1 zu gehen)
quelle
Sie sollten Ihr privates Sperrobjekt nicht über eine Eigenschaft veröffentlichen. Das Sperrobjekt sollte privat existieren, um ausschließlich als Treffpunkt zu fungieren.
Wenn sich die Leistung bei Verwendung des Standardschlosses als schlecht herausstellt, kann die Power Threading- Schlosssammlung von Wintellect sehr nützlich sein.
quelle
Es gibt verschiedene Probleme mit der von Ihnen beschriebenen Implementierungsmethode.
Persönlich habe ich festgestellt, dass der beste Weg, eine thread-sichere Klasse zu implementieren, die Unveränderlichkeit ist. Es reduziert wirklich die Anzahl der Probleme, auf die Sie bei der Thread-Sicherheit stoßen können. Weitere Informationen finden Sie in Eric Lipperts Blog .
quelle
Sie müssen die SyncRoot-Eigenschaft in Ihren Consumer-Objekten nicht sperren. Die Sperre, die Sie innerhalb der Methoden des Wörterbuchs haben, ist ausreichend.
Ausarbeiten: Was am Ende passiert, ist, dass Ihr Wörterbuch für einen längeren Zeitraum als nötig gesperrt ist.
Was in Ihrem Fall passiert, ist Folgendes:
Angenommen, Thread A erhält die Sperre für SyncRoot vor dem Aufruf von m_mySharedDictionary.Add. Thread B versucht dann, die Sperre zu erlangen, ist jedoch blockiert. Tatsächlich sind alle anderen Threads blockiert. Thread A darf die Add-Methode aufrufen. Bei der lock-Anweisung innerhalb der Add-Methode darf Thread A die Sperre erneut erhalten, da sie sie bereits besitzt. Beim Verlassen des Sperrkontexts innerhalb der Methode und außerhalb der Methode hat Thread A alle Sperren freigegeben, sodass andere Threads fortgesetzt werden können.
Sie können einfach jedem Verbraucher erlauben, die Add-Methode aufzurufen, da die Lock-Anweisung in Ihrer SharedDictionary-Klasse Add-Methode den gleichen Effekt hat. Zu diesem Zeitpunkt haben Sie redundante Sperren. Sie würden SyncRoot nur außerhalb einer der Wörterbuchmethoden sperren, wenn Sie zwei Vorgänge für das Wörterbuchobjekt ausführen müssten, die garantiert nacheinander ausgeführt werden müssen.
quelle
Nur ein Gedanke, warum nicht das Wörterbuch neu erstellen? Wenn das Lesen eine Vielzahl von Schreibvorgängen ist, werden durch das Sperren alle Anforderungen synchronisiert.
Beispiel
quelle
Sammlungen und Synchronisation
quelle