Kdy použít kolekce pro bezpečný přístup z více vláken
.NET Framework verze 4 zavádí pět nových typů operací, které jsou speciálně navrženy tak, aby podporovaly vícevláknové přidávání a vícevláknové odstraňování. Tyto nové typy používají různé druhy efektivních zamykacích a bezzámkových synchronizačních mechanizmů pro dosažení bezpečného přístupu z více vláken. Synchronizace přidává k operaci režii. Množství režie závisí na druhu synchronizace, která se používá a na druhu operací, které jsou prováděny, a ostatních faktorech, jako je například počet vláken, které se pokouší současně získat přístup ke kolekci.
V některých scénářích je režie synchronizace zanedbatelná a umožňuje, aby vícevláknový typ pracoval výrazně rychleji a mohl mnohem lépe měnit velikost než ekvivalentní případ bez bezpečného přístupu z více vláken, která používají externí zámek. V jiných případech může režie způsobit pro bezpečný přístup z více vláken zhruba stejnou nebo případně i nižší rychlost provádění než verze bez bezpečného přístupu z více vláken, která používá externí zámky.
Následující části obsahují obecné pokyny o tom, kdy použít kolekci pro vlákna s bezpečným přístupem oproti jejímu ekvivalentu bez bezpečného přístupu z více vláken, které mají uživatelský zámek pro operace čtení a zápisu. Protože výkon se může lišit v závislosti na mnoha faktorech, pokyny nejsou konkrétní a nejsou nutně platné za všech okolností. Pokud je výkon velmi důležitý, pak nejlepším způsobem k určení typu kolekce je měření výkonu založené na reprezentativní konfiguraci počítače a jeho zatížení. Tento dokument používá následující výrazy:
Čistý scénář producent-příjemce
Jakékoli dané vlákno buď přidává nebo odebírá prvky, ale ne obojí.Smíšený scénář producent-příjemce
Jakékoli dané vlákno jak přidává tak odebírá prvky.Zrychlení
Rychlejší algoritmický výkon vzhledem k jinému typu ve stejném scénáři.Škálovatelnost
Zvýšení výkonu, které je úměrné počtu jader v počítači. Algoritmus, který provádí změnu rychleji prostřednictvím osmi jáder než prostřednictvím dvou jáder.
ConcurrentQueue(T) vs.Fronta(T)
V čistých scénářích producent-příjemce, kde doba zpracování pro každý element je velmi malá (několik instrukcí), System.Collections.Concurrent.ConcurrentQueue<T> může nabídnout menší výkony oproti System.Collections.Generic.Queue<T>, která má externí zámek. V tomto scénáři ConcurrentQueue<T> podává nejlepší výkon, pokud jedno vyhrazené vlákno zařazuje do fronty a jedno vyhrazené vlákno vyřazuje z fronty. Pokud toto pravidlo nevynutíte, pak Queue<T> může dokonce podávat mírně vyšší výkon než ConcurrentQueue<T> v počítačích s více jádry.
Pokud je doba zpracování okolo 500 FLOPS (operace s plovoucí desetinnou čárkou) nebo delší, pak se pravidlo dvou vláken nevztahuje k ConcurrentQueue<T>, která pak má velmi dobrou škálovatelnost. Queue<T> není dobře škálovatelné v tomto scénáři.
Ve smíšených scénářích producent-příjemce, kdy je čas zpracování velmi malý, Queue<T>, která má externí zámek, poskytuje lepší škálovatelnost než ConcurrentQueue<T>. Avšak, pokud je čas zpracování okolo 500 FLOPS nebo více, pak ConcurrentQueue<T> poskytuje lepší škálovatelnost.
ConcurrentStack vs.Zásobník
V čistém scénáři producent-příjemce, kdy je čas zpracování velmi malý, pak System.Collections.Concurrent.ConcurrentStack<T> a System.Collections.Generic.Stack<T>, které mají externí zámek, budou mít pravděpodobně zhruba stejný výkon s jedním vláknem vyhrazeným pro operace vložení a druhým vláknem vyhrazeným pro operace vyjmutí. Avšak s rostoucím počem vláken se oba typy zpomalují kvůli rostoucím sporům a Stack<T> může mít lepší výkon než ConcurrentStack<T>. Pokud je doba zpracování okolo 500 FLOPS nebo více, pak jsou oba typy zhruba stejně škálovatelné.
Ve smíšených scénářích producent-příjemce je ConcurrentStack<T> rychlejší jak pro malou, tak pro velkou zátěž.
Použití PushRange a TryPopRangemůže výrazně zrychlit přístup časy.
ConcurrentDictionary vs.Slovník
Obecně používejte System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue> v jakémkoli scénáři, kde přidáváte a aktualizujete klíče nebo hodnoty souběžně z více vláken. Ve scénářích, které obsahují časté aktualizace a relativně malý počet čtení, ConcurrentDictionary<TKey, TValue> obecně nabízí mírné výhody. Ve scénářích, které obsahují mnoho čtení a mnoho aktualizací, je ConcurrentDictionary<TKey, TValue> obecně výrazně rychlejší v počítačích s libovolným počtem jáder.
V situacích, které obsahují časté aktualizace, můžete zvýšit stupeň souběžnosti v ConcurrentDictionary<TKey, TValue> a pak změřit, jestli se výkon zvýšil v počítačích s více jádry. Pokud změníte úroveň souběžnosti, vyhněte se co nejvíce globálním operacím.
Pokud pouze čtete klíče nebo hodnoty, pak je Dictionary<TKey, TValue> rychlejší, protože žádná synchronizace není vyžadována, pokud není slovník upravován žádným vlákem.
ConcurrentBag
V čistém scénáři producent-příjemce bude System.Collections.Concurrent.ConcurrentBag<T> mít pravděpodobně menší výkon než jiné typy souběžných kolekcí.
Ve smíšených scénářích producent-příjemce je ConcurrentBag<T> zpravidla mnohem rychlejší a lépe škálovatelné než jiné souběžné typy kolekcí pro jak velké, tak malé zatížení.
BlockingCollection
Pokud je vyžadována sémantika ohraničování a blokování, pak System.Collections.Concurrent.BlockingCollection<T> bude pravděpodobně rychlejší než jakákoli vlastní implementace. Podporuje to také rich cancellation, výčty a obslužné rutiny výjimek.