Verwendung einer threadsicheren Auflistung
.NET Framework, Version 4 beinhaltet fünf neue Auflistungstypen, die speziell für die Unterstützung von multithreadbezogenen Hinzufüge- und Entfernungsvorgängen konzipiert wurden. Zur Gewährleistung von Threadsicherheit verwenden diese neuen Typen unterschiedliche Arten effizienter sperrender und sperrfreier Synchronisierungsmechanismen. Ein Vorgang wird durch Synchronisierung aufwändiger. Das Ausmaß des Aufwands hängt von der Art der verwendeten Synchronisierung, der Art der ausgeführten Vorgänge und von anderen Faktoren ab, z. B. der Anzahl der Threads, die versuchen, gleichzeitig auf die Auflistung zuzugreifen.
In einigen Szenarien ist der Synchronisierungsaufwand unwesentlich und ermöglicht eine deutlich schnellere Ausführung und bessere Skalierbarkeit des Multithreadtyps als beim nicht sicheren Äquivalent, wenn ein Schutz durch eine externe Sperre besteht. In anderen Szenarien kann der Aufwand dazu führen, dass der threadsichere Typ in etwa mit der gleichen oder sogar einer geringeren Leistung und Skalierbarkeit ausgeführt wird wie die extern gesperrte, nicht threadsichere Version des Typs.
Die folgenden Abschnitte enthalten allgemeine Hinweise dazu, wann eine threadsichere Auflistung anstelle ihres nicht threadsicheren Äquivalents verwendet wird, das eine vom Benutzer bereitgestellte Sperre um die Lese- und Schreibvorgänge besitzt. Da die Leistung von vielen Faktoren abhängig sein kann, handelt es sich nicht um eine spezielle Anleitung, die daher nicht unter allen Umständen gültig ist. Wenn die Leistung sehr wichtig ist, ist die am besten geeignete Methode zur Bestimmung des zu verwendenden Auflistungstyps die Messung der Leistung auf Basis von repräsentativen Computerkonfigurationen und Lasten. In diesem Dokument werden die folgenden Begriffe verwendet:
Reines Producer-Consumer-Szenario
In jedem angegebenen Thread werden Elemente entweder hinzugefügt oder entfernt, es finden jedoch nicht beide Vorgänge statt.Gemischtes Producer-Consumer-Szenario
In jedem angegebenen Thread werden Elemente sowohl hinzugefügt als auch entfernt.Beschleunigung
Schnellere algorithmische Leistung relativ zu einem anderen Typ im gleichen Szenario.Skalierbarkeit
Die Zunahme der Leistung, die proportional zur Anzahl der Kerne auf dem Computer ist. Mit einem Algorithmus, der skaliert wird, werden bei acht Kernen schnellere Leistungen erzielt als bei zwei Kernen.
ConcurrentQueue(T) im Vergleich zuQueue(T)
In reinen Producer-Consumer-Szenarien, in denen die Verarbeitungszeit für jedes Element sehr klein ist (einige Anweisungen), kann ein System.Collections.Concurrent.ConcurrentQueue<T>-Objekt geringfügige Leistungsvorteile gegenüber einem System.Collections.Generic.Queue<T>-Objekt mit einer externen Sperre erreichen. In diesem Szenario erzielt ConcurrentQueue<T> die beste Leistung, wenn sich ein dedizierter Thread in der Warteschlange befindet und ein dedizierter Thread die Warteschlange verlässt. Wenn Sie diese Regel nicht erzwingen, kann Queue<T> sogar ein wenig schneller als ConcurrentQueue<T> auf Computern mit mehreren Kernen ausgeführt werden.
Wenn die Verarbeitungszeit bei etwa 500 FLOPS (Gleitkommavorgänge) oder höher liegt, gilt die Zwei-Thread-Regel nicht für das ConcurrentQueue<T>-Objekt, das dann über eine sehr gute Skalierbarkeit verfügt. Queue<T> lässt sich in diesem Szenario nicht vorteilhaft skalieren.
Bei sehr geringer Verarbeitungszeit zeichnet sich ein Queue<T>-Objekt mit einer externen Sperre in gemischten Producer-Consumer-Szenarien durch eine bessere Skalierbarkeit als ein ConcurrentQueue<T>-Objekt aus. Wenn die Verarbeitungszeit jedoch bei etwa 500 FLOPS oder darüber liegt, kann das ConcurrentQueue<T>-Objekt besser skaliert werden.
ConcurrentStack im Vergleich zuStack
In reinen Producer-Consumer-Szenarien erzielen das System.Collections.Concurrent.ConcurrentStack<T>-Objekt und das System.Collections.Generic.Stack<T>-Objekt, das eine externe Sperre besitzt, bei sehr geringer Verarbeitungszeit wahrscheinlich annähernd die gleiche Leistung mit einem dedizierten Thread für Ablegevorgänge und einem dedizierten Thread für Abholvorgänge. Bei zunehmender Anzahl der Threads werden jedoch beide Typen aufgrund des stärkeren Konflikts langsamer, und mit Stack<T> werden unter Umständen bessere Leistungen als mit ConcurrentStack<T> erzielt. Wenn die Verarbeitungszeit bei rund 500 FLOPS oder darüber liegt, werden beide Typen mit der etwa gleichen Rate skaliert.
In gemischten Producer-Consumer-Szenarien ist ConcurrentStack<T> sowohl für kleine als auch für große Arbeitslasten schneller.
Die Verwendung der PushRange-Methode und der TryPopRange-Methode kann die Zugriffszeiten unter Umständen erheblich beschleunigen.
ConcurrentDictionary im Vergleich zuDictionary
Verwenden Sie im Allgemeinen in jedem Szenario, in dem Sie Schlüssel oder Werte gleichzeitig von mehreren Threads hinzufügen und aktualisieren, ein System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>-Objekt. In Szenarien, die häufige Updates und relativ wenige Lesevorgänge umfassen, bietet das ConcurrentDictionary<TKey, TValue>-Objekt in der Regel geringfügige Vorteile. In Szenarien, die zahlreiche Lesevorgänge und Updates umfassen, wird das ConcurrentDictionary<TKey, TValue>-Objekt im Allgemeinen auf Computern bedeutend schneller, die über eine beliebige Anzahl von Kernen verfügen.
In Szenarien, die häufige Updates umfassen, können Sie den Grad der Parallelität im ConcurrentDictionary<TKey, TValue>-Objekt erhöhen und anschließend ermitteln, ob sich die Leistung auf Computern mit einer größeren Anzahl von Kernen verbessert. Wenn Sie die Parallelitätsebene ändern, vermeiden Sie so weit wie möglich globale Vorgänge.
Wenn Sie nur Schlüssel oder Werte lesen, ist das Dictionary<TKey, TValue>-Objekt schneller, da keine Synchronisierung erforderlich ist, wenn das Wörterbuch nicht von Threads geändert wird.
ConcurrentBag
In reinen Producer-Consumer-Szenarien ist das System.Collections.Concurrent.ConcurrentBag<T>-Objekt wahrscheinlich langsamer als die anderen gleichzeitigen Auflistungstypen.
In gemischten Producer-Consumer-Szenarien ist das ConcurrentBag<T>-Objekt sowohl bei großen als auch bei kleinen Arbeitslasten im Allgemeinen viel schneller und besser skalierbar als ein beliebiger anderer gleichzeitiger Auflistungstyp.
BlockingCollection
Wenn Begrenzungs- und Blockierungssemantiken erforderlich sind, wird das System.Collections.Concurrent.BlockingCollection<T>-Objekt wahrscheinlich schneller als jede beliebige benutzerdefinierte Implementierung ausgeführt. Zudem werden umfassende Abbruchmöglichkeiten, Enumeration und Ausnahmebehandlung unterstützt.