Heim > Artikel > Backend-Entwicklung > .NET-Multithread-Programmierung – gleichzeitige Sammlungen
Gleichzeitige Sammlungen
1 Warum gleichzeitige Sammlungen verwenden?
Die Hauptgründe sind wie folgt:
Die klassischen Listen, Sammlungen und Arrays, die in den Namespaces System.Collections und System.Collections.Generic bereitgestellt werden, sind nicht threadsicher Ohne einen Synchronisationsmechanismus sind sie nicht für die Annahme gleichzeitiger Anweisungen zum Hinzufügen und Entfernen von Elementen geeignet.
Die Verwendung der oben genannten klassischen Sammlungen im gleichzeitigen Code erfordert eine komplexe Synchronisationsverwaltung, die sehr unpraktisch zu verwenden ist.
Die Verwendung komplexer Synchronisierungsmechanismen führt zu erheblichen Leistungseinbußen.
Die neuen Sammlungen von NET Framework 4 minimieren die Notwendigkeit, Sperren zu verwenden, so weit wie möglich. Diese neuen Sammlungen vermeiden die Verwendung sich gegenseitig ausschließender Schwergewichtssperren durch die Verwendung von Compare-and-Swap-Anweisungen (CAS) und Speicherbarrieren. Das garantiert Leistung.
Hinweis: Gleichzeitige Sammlungen haben im Vergleich zu klassischen Sammlungen einen größeren Overhead, daher ist die Verwendung gleichzeitiger Sammlungen im seriellen Code sinnlos und erhöht nur den zusätzlichen Overhead und beschleunigt den Vorgang. Langsamer als der Zugriff auf die klassische Sammlung Sammlung.
2 gleichzeitige Sammlungen
1) ConcurrentQueue: Thread-sichere First-in-First-out (FIFO)-Sammlung
Hauptmethoden:
Enqueue(T item); Fügt das Objekt am Ende der Sammlung hinzu.
TryDequeue(out T result); Versuchen Sie, das Objekt zu Beginn der Sammlung zu entfernen und zurückzugeben. Der Rückgabewert gibt an, ob der Vorgang erfolgreich war.
TryPeek(out T result); Versuchen Sie, das Objekt am Anfang der Sammlung zurückzugeben, ohne es zu entfernen. Der Rückgabewert gibt an, ob der Vorgang erfolgreich war.
Beschreibung:
ConcurrentQueue ist völlig sperrenfrei, aber wenn ein CAS-Vorgang fehlschlägt und ein Ressourcenkonflikt auftritt, dreht er sich möglicherweise und versucht es erneut Betrieb.
ConcurrentQueue ist eine FIFO-Sammlung. In einigen Situationen, die nichts mit der Reihenfolge des Ein- und Ausstiegs zu tun haben, sollten Sie ConcurrentQueue nicht verwenden.
2) ConcurrentStack: Thread-sichere Last-In-First-Out (LIFO)-Sammlung
Hauptmethoden und Attribute:
Push (T-Element); Fügt das Objekt oben in die Sammlung ein.
TryPop(out T result); Versuchen Sie, das Objekt oben in der Sammlung zu platzieren und zurückzugeben. Der Rückgabewert gibt an, ob der Vorgang erfolgreich ist.
TryPeek(out T result); Versuchen Sie, das Objekt am Anfang der Sammlung zurückzugeben, ohne es zu entfernen. Der Rückgabewert gibt an, ob der Vorgang erfolgreich war.
IsEmpty { get; } Gibt an, ob die Sammlung leer ist.
PushRange(T[] items); Fügt mehrere Objekte oben in die Sammlung ein.
TryPopRange(T[] items); Fügt mehrere Elemente oben ein, und das Rückgabeergebnis ist die Anzahl der angezeigten Elemente.
Hinweis:
Ähnlich wie ConcurrentQueue ist ConcurrentStack völlig sperrenfrei, aber wenn ein CAS-Vorgang fehlschlägt und Ressourcenkonflikte auftreten, kann dies passieren Wird den Vorgang drehen und wiederholen.
Um zu ermitteln, ob die Sammlung Elemente enthält, verwenden Sie die IsEmpty-Eigenschaft, anstatt zu beurteilen, ob die Count-Eigenschaft größer als Null ist. Der Aufruf von Count ist teurer als der Aufruf von IsEmpty.
Beachten Sie den zusätzlichen Overhead und den zusätzlichen Speicherverbrauch, der durch die Pufferung verursacht wird, wenn Sie PushRange(T[] items) und TryPopRange(T[] items) verwenden.
3) ConcurrentBag: eine ungeordnete Sammlung mit wiederholbaren Elementen
Hauptmethoden und Attribute:
TryPeek(out T result ); versucht, ein Objekt aus der Sammlung zurückzugeben, ohne das Objekt zu entfernen. Der Rückgabewert gibt an, ob das Objekt erfolgreich abgerufen wurde.
TryTake(out T result); Versuchen Sie, ein Objekt aus der Sammlung zurückzugeben und das Objekt zu entfernen. Der Rückgabewert gibt an, ob das Objekt erfolgreich abgerufen wurde.
Add(T item); Fügt das Objekt zur Sammlung hinzu.
IsEmpty { get; } Die Erklärung ist die gleiche wie bei ConcurrentStack
Beschreibung:
ConcurrentBag für jeden Thread, der auf die Sammlung zugreift, verwaltet eine lokale Warteschlange und greift, wenn möglich, sperrenfrei auf die lokale Warteschlange zu.
ConcurrentBag ist sehr effizient beim Hinzufügen und Entfernen von Elementen im selben Thread.
Da ConcurrentBag manchmal Sperren erfordert, ist es in Szenarien, in denen Producer-Threads und Consumer-Threads vollständig getrennt sind, sehr ineffizient.
Der Aufruf von ConcurrentBag an IsEmpty ist sehr teuer, da dafür vorübergehend alle Sperren dieser ungeordneten Gruppe erworben werden müssen.
4) BlockingCollection: Eine Thread-sichere Sammlung, die
System.Collections.Concurrent.IProducerConsumerCollection
Hauptsächlich Methoden und Eigenschaften:
BlockingCollection(int BoundedCapacity); stellt die Sammlungsgrenzgröße dar.
CompleteAdding(); Markiert die BlockingCollection-Instanz als keine Ergänzungen mehr akzeptierend.
IsCompleted { get; } Ob diese Sammlung als abgeschlossen und leer markiert wurde.
GetConsumingEnumerable(); Entfernt das entfernte Element und gibt es aus der Sammlung zurück.
Add(T item);
TryTake(T item, int millisecondsTimeout, CancellationToken cancellingToken);
Anweisungen:
Use BlockingCollection( ) Konstruktor instanziiert BlockingCollection, was bedeutet, dass BoundedCapacity nicht festgelegt ist, dann ist BoundedCapacity der Standardwert: int.MaxValue.
Bounded: Verwenden Sie BlockingCollection(int BoundedCapacity), um den Wert von BoundedCapacity festzulegen. Wenn die Sammlungskapazität diesen Wert erreicht, wird der Thread, der Elemente zu BlockingCollection hinzufügt, blockiert, bis ein Element gelöscht wird. .
Die Begrenzungsfunktion steuert die maximale Größe der Sammlung im Speicher, was sehr nützlich ist, wenn eine große Anzahl von Elementen verarbeitet werden muss.
Standardmäßig kapselt BlockingCollection eine ConcurrentQueue. Sie können eine gleichzeitige Sammlung angeben, die die IProducerConsumerCollection-Schnittstelle im Konstruktor implementiert, einschließlich: ConcurrentStack und ConcurrentBag.
Die Verwendung dieser Sammlung birgt das Risiko, auf unbestimmte Zeit zu warten. Daher ist die Verwendung von TryTake bequemer, da TryTake eine Zeitüberschreitungskontrolle bietet und ein Element innerhalb einer bestimmten Zeit aus der Sammlung entfernt werden kann . ist wahr; andernfalls ist es falsch.
5) ConcurrentDictionary: Eine threadsichere Sammlung von Schlüssel-Wert-Paaren, auf die mehrere Threads gleichzeitig zugreifen können.
Hauptmethode
AddOrUpdate(TKey key, TValue addValue, Func
GetOrAdd(TKey key, TValue value); Fügt dem Wörterbuch ein Schlüssel/Wert-Paar hinzu, wenn der angegebene Schlüssel noch nicht vorhanden ist.
TryRemove(TKey key, out TValue value);Versuchen Sie, den Wert mit dem angegebenen Schlüssel aus dem Wörterbuch zu entfernen und zurückzugeben.
TryUpdate(TKey key, TValue newValue, TValueVergleichswert); Vergleicht den vorhandenen Wert des angegebenen Schlüssels mit dem angegebenen Wert und aktualisiert den Schlüssel bei Gleichheit mit dem dritten Wert.
Hinweis:
ConcurrentDictionary ist für Lesevorgänge völlig sperrenfrei. ConcurrentDictionary verwendet fein abgestufte Sperren, wenn mehrere Aufgaben oder Threads Elemente hinzufügen oder Daten ändern. Durch die Verwendung feinkörniger Sperren wird nur der Teil gesperrt, der wirklich gesperrt werden muss, nicht das gesamte Wörterbuch.
6) IProducerConsumerCollection: Definiert Methoden für Produzenten/Konsumenten, um threadsichere Sammlungen zu betreiben. Diese Schnittstelle bietet eine einheitliche Darstellung (für Produzenten-/Konsumentensammlungen), sodass Abstraktionen auf höherer Ebene wie System.Collections.Concurrent.BlockingCollection
3. Gemeinsame Muster
1) Paralleles Produzenten-Konsumenten-Muster
Definition:
Der Produzent und der Konsument sind in diesem Muster zwei Arten von Objektmodellen , der Verbraucher hängt von den Ergebnissen des Produzenten ab, während der Produzent die Ergebnisse generiert, verwendet der Verbraucher die Ergebnisse.
Abbildung 1 Paralleles Producer-Consumer-Modell
Erklärung:
Hier wird die gleichzeitige Sammlung verwendet. Das Muster ist a Gute Passform, da gleichzeitige Sammlungen parallele Operationen für Objekte in diesem Muster unterstützen.
Wenn Sie keine gleichzeitigen Sammlungen verwenden, müssen Sie einen Synchronisierungsmechanismus hinzufügen, der das Programm komplexer, schwieriger zu warten und zu verstehen macht und die Leistung erheblich verringert.
Das obige Bild ist ein schematisches Diagramm des Produzenten-Konsumenten-Modells. Die vertikale Achse ist die Zeitachse. Der Produzent und der Konsument befinden sich nicht auf derselben Zeitachse, sondern überschneiden sich soll zeigen: Der Generator erzeugt zunächst Ergebnisse, und dann verwendet der Verbraucher tatsächlich die vom Generator generierten Daten.
2) Pipeline-Muster
Definition:
Eine Pipeline besteht aus mehreren Stufen, jede Stufe besteht aus einer Reihe von Produzenten und Konsumenten. Im Allgemeinen ist die vorherige Stufe der Generator der späteren Stufe. Abhängig von der Pufferwarteschlange zwischen zwei benachbarten Stufen kann jede Stufe gleichzeitig ausgeführt werden.
Abbildung 2 Paralleler Pipeline-Modus
Erklärung:
BlockingCollection
Die Geschwindigkeit der Pipeline entspricht ungefähr der Geschwindigkeit der langsamsten Stufe der Pipeline.
Das obige Bild ist ein schematisches Diagramm des Pipeline-Modus. Die vorherige Stufe ist der Generator der späteren Stufe. Hier können komplexere Modi angezeigt werden Es wird davon ausgegangen, dass jede Phase eine weitere Verarbeitung der Daten umfasst.
4 Verwendung
Nur ConcurrentBag und BlockingCollection dienen als Beispiele, andere gleichzeitige Sammlungen sind ähnlich.
ConcurrentBag
List<string> list = ...... ConcurrentBag<string> bags = new ConcurrentBag<string>(); Parallel.ForEach(list, (item) => { //对list中的每个元素进行处理然后,加入bags中 bags.Add(itemAfter); });
BlockingCollection – Producer Consumer Pattern
public static void Execute() { //调用Invoke,使得生产者任务和消费者任务并行执行 //Producer方法和Customer方法在Invoke中的参数顺序任意,不论何种顺序都会获得正确的结果 Parallel.Invoke(()=>Customer(),()=>Producer()); Console.WriteLine(string.Join(",",customerColl)); } //生产者集合 private static BlockingCollection<int> producerColl = new BlockingCollection<int>(); //消费者集合 private static BlockingCollection<string> customerColl = new BlockingCollection<string>(); public static void Producer() { //循环将数据加入生成者集合 for (int i = 0; i < 100; i++) { producerColl.Add(i); } //设置信号,表明不在向生产者集合中加入新数据 //可以设置更加复杂的通知形式,比如数据量达到一定值且其中的数据满足某一条件时就设置完成添加 producerColl.CompleteAdding(); } public static void Customer() { //调用IsCompleted方法,判断生产者集合是否在添加数据,是否还有未"消费"的数据 //注意不要使用IsAddingCompleted,IsAddingCompleted只表明集合标记为已完成添加,而不能说明其为空 //而IsCompleted为ture时,那么IsAddingCompleted为ture且集合为空 while (!producerColl.IsCompleted) { //调用Take或TryTake "消费"数据,消费一个,移除一个 //TryAdd的好处是提供超时机制 customerColl.Add(string.Format("消费:{0}", producerColl.Take())); } }
Das Obige ist der Inhalt der .NET-Multithread-Programmierung – gleichzeitige Sammlung. Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn)!