Wanneer gebruikt u een thread-veilige verzameling
.NET Framework 4 heeft vijf verzamelingstypen geïntroduceerd die speciaal zijn ontworpen ter ondersteuning van bewerkingen met meerdere threads voor toevoegen en verwijderen. Voor het bereiken van thread-veiligheid gebruiken deze typen verschillende soorten efficiënte vergrendeling en vergrendelingsvrije synchronisatiemechanismen. Synchronisatie voegt overhead toe aan een bewerking. De hoeveelheid overhead is afhankelijk van het type synchronisatie dat wordt gebruikt, het soort bewerkingen dat wordt uitgevoerd en andere factoren, zoals het aantal threads dat gelijktijdig toegang probeert te krijgen tot de verzameling.
In sommige scenario's is de overhead van synchronisatie te verwaarlozen en kan het type met meerdere threads aanzienlijk sneller presteren en veel beter schalen dan het niet-thread-veilige equivalent wanneer het wordt beveiligd door een externe vergrendeling. In andere scenario's kan de overhead ertoe leiden dat het threadveilige type ongeveer hetzelfde of zelfs langzamer wordt uitgevoerd en geschaald dan de extern vergrendelde, niet-threadveilige versie van het type.
In de volgende secties vindt u algemene richtlijnen over het gebruik van een thread-veilige verzameling ten opzichte van het niet-threadveilige equivalent met een door de gebruiker geleverde vergrendeling rond de lees- en schrijfbewerkingen. Omdat de prestaties kunnen variëren afhankelijk van veel factoren, zijn de richtlijnen niet specifiek en zijn ze in alle omstandigheden niet noodzakelijkerwijs geldig. Als de prestaties erg belangrijk zijn, kunt u het beste bepalen welk verzamelingstype moet worden gebruikt om de prestaties te meten op basis van representatieve computerconfiguraties en belastingen. In dit document worden de volgende termen gebruikt:
Puur scenario voor producent-consument
Een bepaalde thread voegt elementen toe of verwijdert, maar niet beide.
Scenario voor gemengde producent-consument
Een bepaalde thread voegt elementen toe en verwijdert deze.
Speedup
Snellere algoritmen ten opzichte van een ander type in hetzelfde scenario.
Schaalbaarheid
De toename van de prestaties die evenredig is met het aantal kernen op de computer. Een algoritme dat sneller schaalt op acht kernen dan op twee kernen.
ConcurrentQueue(T) versus Queue(T)
In pure producer-consumerscenario's, waarbij de verwerkingstijd voor elk element zeer klein is (een paar instructies), kunnen dan System.Collections.Concurrent.ConcurrentQueue<T> bescheiden prestatievoordelen bieden ten opzichte van een die System.Collections.Generic.Queue<T> een externe vergrendeling heeft. In dit scenario ConcurrentQueue<T> wordt het beste uitgevoerd wanneer één toegewezen thread in de wachtrij staat en één toegewezen thread de wachtrij ongedaan maakt. Als u deze regel niet afdwingt, Queue<T> kan dit zelfs iets sneller werken dan ConcurrentQueue<T> op computers met meerdere kernen.
Wanneer de verwerkingstijd ongeveer 500 FLOPS -bewerkingen (drijvendekommabewerkingen) of meer is, is de regel met twee threads niet van toepassing ConcurrentQueue<T>op , die vervolgens zeer goede schaalbaarheid heeft. Queue<T> schaalt in dit scenario niet goed.
In scenario's voor gemengde producenten en consumenten, wanneer de verwerkingstijd erg klein is, is een Queue<T> die een externe vergrendeling beter schaalt dan ConcurrentQueue<T> wel. Wanneer de verwerkingstijd echter ongeveer 500 FLOPS of meer is, is de ConcurrentQueue<T> schaal beter.
ConcurrentStack versus Stack
In pure producer-consumerscenario's, wanneer de verwerkingstijd erg klein is, System.Collections.Concurrent.ConcurrentStack<T> en System.Collections.Generic.Stack<T> die een externe vergrendeling heeft, zal waarschijnlijk ongeveer hetzelfde worden uitgevoerd met één toegewezen pushingthread en één toegewezen popping-thread. Naarmate het aantal threads toeneemt, vertragen beide typen echter vanwege toegenomen conflicten en Stack<T> kunnen ze beter presteren dan ConcurrentStack<T>. Wanneer de verwerkingstijd ongeveer 500 FLOPS of meer is, worden beide typen met ongeveer dezelfde snelheid geschaald.
In scenario's ConcurrentStack<T> voor gemengde producenten en consumenten is het sneller voor zowel kleine als grote workloads.
Het gebruik van de PushRange en TryPopRange kan de toegangstijden aanzienlijk versnellen.
Gelijktijdigdictionary versus woordenlijst
Gebruik in het algemeen een System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue> scenario waarin u sleutels of waarden tegelijk toevoegt en bijwerkt vanuit meerdere threads. In scenario's met frequente updates en relatief weinig leesbewerkingen biedt het over het ConcurrentDictionary<TKey,TValue> algemeen bescheiden voordelen. In scenario's met veel leesbewerkingen en veel updates is het over het ConcurrentDictionary<TKey,TValue> algemeen aanzienlijk sneller op computers met een willekeurig aantal kernen.
In scenario's met frequente updates kunt u de mate van gelijktijdigheid in de ConcurrentDictionary<TKey,TValue> en vervolgens meten om te zien of de prestaties toenemen op computers met meer kernen. Als u het gelijktijdigheidsniveau wijzigt, vermijdt u zo veel mogelijk globale bewerkingen.
Als u alleen sleutel of waarden leest, is dit Dictionary<TKey,TValue> sneller omdat er geen synchronisatie is vereist als de woordenlijst niet wordt gewijzigd door threads.
ConcurrentBag
In pure producer-consumerscenario's System.Collections.Concurrent.ConcurrentBag<T> wordt waarschijnlijk langzamer uitgevoerd dan de andere gelijktijdige verzamelingstypen.
In scenario's ConcurrentBag<T> voor gemengde producenten-consumenten is over het algemeen veel sneller en schaalbaarder dan elk ander gelijktijdig verzamelingstype voor zowel grote als kleine workloads.
BlockingCollection
Wanneer semantiek voor begrenzing en blokkering vereist is, System.Collections.Concurrent.BlockingCollection<T> zal dit waarschijnlijk sneller worden uitgevoerd dan elke aangepaste implementatie. Het biedt ook ondersteuning voor uitgebreide annulering, opsomming en afhandeling van uitzonderingen.