Sdílet prostřednictvím


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.

Viz také

Odkaz

System.Collections.Concurrent

Koncepty

Kolekce pro bezpečný přístup z více vláken

Paralelní programování v rozhraní .NET Framework