Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
.NET Framework 4 hat fünf Sammlungstypen eingeführt, die speziell für die Unterstützung von Multithread-Add- und Remove-Vorgängen entwickelt wurden. Um Thread-Sicherheit zu erreichen, verwenden diese Typen verschiedene Arten von effizienten Sperr- und sperrfreien Synchronisierungsmechanismen. Die Synchronisierung erhöht den Aufwand für einen Vorgang. Der Aufwand hängt von der Art der Synchronisierung ab, die verwendet wird, die Art der ausgeführten Vorgänge und andere Faktoren, z. B. die Anzahl der Threads, die gleichzeitig auf die Auflistung zugreifen möchten.
In einigen Szenarien ist der Synchronisierungsaufwand vernachlässigbar und ermöglicht es, dass der multithreaded Typ wesentlich schneller arbeitet und wesentlich besser skaliert als sein nicht thread-sicheres Gegenstück, wenn er durch eine externe Sperre geschützt wird. In anderen Szenarien kann der Overhead dazu führen, dass der threadsichere Typ in etwa gleich oder sogar langsamer arbeitet und skaliert als die extern gesperrte, nicht threadsichere Version des Typs.
In den folgenden Abschnitten finden Sie allgemeine Hinweise dazu, wann Sie eine threadsichere Auflistung verwenden sollten im Vergleich zu einer nicht threadsicheren Entsprechung, die eine vom Benutzer bereitgestellte Sperre für die Lese- und Schreibvorgänge verwendet. Da die Leistung je nach vielen Faktoren variieren kann, ist die Anleitung nicht spezifisch und in allen Umständen nicht notwendigerweise gültig. Wenn die Leistung sehr wichtig ist, können Sie am besten ermitteln, welcher Sammlungstyp verwendet werden soll, um die Leistung basierend auf repräsentativen Computerkonfigurationen und Lasten zu messen. In diesem Dokument werden die folgenden Begriffe verwendet:
Reines Produzenten-Konsumenten-Szenario
Jeder gegebene Thread fügt elemente hinzu oder entfernt, aber nicht beide.
Szenario für gemischte Produzenten und Verbraucher
Jeder gegebene Thread fügt Elemente hinzu und entfernt sie.
Beschleunigung
Schnellere algorithmische Leistung relativ zu einem anderen Typ im selben Szenario.
Skalierbarkeit
Die Steigerung der Leistung, die proportional zur Anzahl der Kerne auf dem Computer ist. Mit einem Algorithmus, der skaliert wird, werden bei acht Kernen höhere Leistungen erzielt als bei zwei Kernen.
ConcurrentQueue(T) oder Queue(T)
In reinen Produzenten-Konsumenten-Szenarien, in denen die Verarbeitungszeit für jedes Element sehr klein ist (einige Anweisungen), kann System.Collections.Concurrent.ConcurrentQueue<T> gegenüber einem System.Collections.Generic.Queue<T> mit externer Sperre bescheidene Leistungsverbesserungen bieten. 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, könnte Queue<T> auf Computern mit mehreren Kernen möglicherweise sogar etwas schneller als ConcurrentQueue<T> sein.
Wenn die Verarbeitungszeit etwa 500 FLOPS (Gleitkommavorgänge) oder mehr beträgt, gilt die Zweithreadregel nicht für ConcurrentQueue<T>, was dann sehr gute Skalierbarkeit hat. Queue<T> skaliert in diesem Szenario nicht gut.
In gemischten Produzent-Konsument-Szenarien, wenn die Verarbeitungszeit sehr kurz ist, skaliert ein Queue<T> mit externer Sperre besser als ConcurrentQueue<T>. Wenn die Verarbeitungszeit jedoch etwa 500 FLOPS oder mehr beträgt, skaliert ConcurrentQueue<T> besser.
ConcurrentStack oder Stapel
In reinen Producer-Consumer-Szenarien erzielen System.Collections.Concurrent.ConcurrentStack<T> und System.Collections.Generic.Stack<T> mit einer externen Sperre 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. Da die Anzahl der Threads jedoch zunimmt, verlangsamen sich beide Typen aufgrund erhöhter Konkurrenz, und Stack<T> kann möglicherweise besser als ConcurrentStack<T> ausgeführt werden. Wenn die Verarbeitungszeit etwa 500 FLOPS oder mehr beträgt, werden beide Typen mit etwa derselben Rate skaliert.
In Szenarien mit gemischten Produzenten und Verbrauchern ist ConcurrentStack<T> sowohl für kleine als auch für große Workloads schneller.
Die Verwendung der PushRange und TryPopRange kann die Zugriffszeiten erheblich beschleunigen.
ConcurrentDictionary oder Dictionary
Verwenden Sie im Allgemeinen ein System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>, wenn Sie Schlüssel oder Werte gleichzeitig aus mehreren Threads hinzufügen und aktualisieren. In Szenarien mit häufigen Updates und relativ wenigen Lesevorgängen bietet dies ConcurrentDictionary<TKey,TValue> im Allgemeinen bescheidene Vorteile. In Szenarien mit vielen Lesevorgängen und vielen Updates ist ConcurrentDictionary<TKey,TValue> auf Computern mit beliebiger Anzahl von Kernen in der Regel erheblich schneller.
In Szenarien mit häufigen Updates können Sie den Grad der Parallelität erhöhen ConcurrentDictionary<TKey,TValue> und dann messen, ob die Leistung auf Computern mit mehr Kernen steigt. Wenn Sie die Parallelitätsebene ändern, vermeiden Sie globale Vorgänge so weit wie möglich.
Wenn Sie nur Schlüssel oder Werte lesen, ist Dictionary<TKey,TValue> schneller, da keine Synchronisierung erforderlich ist, wenn das Wörterbuch nicht von Threads geändert wird.
Gleichzeitiger Beutel
In reinen Producer-Consumer-Szenarien ist System.Collections.Concurrent.ConcurrentBag<T> wahrscheinlich langsamer als die anderen gleichzeitigen Sammlungstypen.
In gemischten Produzenten-Verbraucher-Szenarien ist ConcurrentBag<T> im Allgemeinen viel schneller und skalierbarer als jeder andere konkurrente Collection-Typ für große und kleine Workloads.
BlockingCollection
Wenn die Begrenzungs- und Blockierungssemantik erforderlich ist, wird System.Collections.Concurrent.BlockingCollection<T> wahrscheinlich schneller sein als eine benutzerdefinierte Implementierung. Zudem werden umfassende Möglichkeiten für die Abbruch-, Enumerations- und Ausnahmebehandlung unterstützt.