Поделиться через


Преимущества использования потокобезопасных коллекций

На платформе .NET Framework 4 доступны пять новых типов коллекций, которые специально разработаны для поддержки многопотоковых операций добавления и удаления. Для достижения потокобезопасности эти новые типы используют различные типы эффективных механизмов синхронизации с блокировкой и без блокировки. Синхронизация добавляет к операции издержки. Значения издержек зависят от используемого типа синхронизации, выполняемого типа операции и других факторов, например, количества потоков, которые одновременно пытаются получить доступ к коллекции.

В некоторых сценариях издержки синхронизации незначительны и позволяют многопотоковым вариантам выполняться значительно быстрее и обеспечивают лучшую масштабируемость, чем в случае потоконебезопасного эквивалента при защите с помощью внешней блокировки. В других сценариях издержки могут вызвать ситуацию, когда потокобезопасный вариант выполняется и масштабируется примерно также и даже более медленно, чем потоконебезопасная версия типа с внешней блокировкой.

В следующих подразделах приводятся общие рекомендации по использованию потокобезопасной коллекции и потоконебезопасного эквивалента, который содержит заданную пользователем блокировку для операций чтения и записи. Так производительность может зависеть от множества факторов, рекомендации нехарактерны и необязательно являются допустимыми во всех обстоятельствах. Если производительность имеет важное значение, то лучшим способом для определения используемого типа коллекции является измерение производительности на основе обычной конфигурации компьютера и нагрузке. В данном документе используются следующие термины.

  • Чистый сценарий "производитель-получатель"
    Все заданные потоки либо добавляют элементы, либо удаляют их, но не то и другое вместе.

  • Смешанный сценарий "производитель-получатель"
    Все заданные потоки добавляют элементы или удаляют их.

  • Ускорение
    Ускорение производительности алгоритма одного типа относительно другого типа в этом же сценарии.

  • Масштабируемость
    Увеличение в производительности, которое пропорционально количеству ядер в компьютере. Масштабируемый алгоритм выполняется быстрее на компьютере у которого восемь ядер, чем на компьютере у которого два ядра.

Класс ConcurrentQueue(T) икласс Queue(T)

В чистых сценариях "производитель-получатель", когда время обработки каждого элемента очень мало (несколько инструкций), класс System.Collections.Concurrent.ConcurrentQueue<T> может предложить незначительный рост производительности по сравнению с классом System.Collections.Generic.Queue<T>, который содержит внешнюю блокировку. В этом сценарии класс ConcurrentQueue<T> выполняется лучше, когда один выделенный поток помещается в очередь, а другой выделенный поток удаляется из очереди. Если это правило не применяется, класс Queue<T> может даже выполняться немного быстрее, чем класс ConcurrentQueue<T> на компьютерах, у которых есть несколько ядер.

Когда время обработки составляет 500 FLOPS (операций с плавающей запятой) или больше, то правило двух потоков не применяется к классу ConcurrentQueue<T>, который имеет очень хорошую масштабируемость. Класс Queue<T> в данном сценарии не имеет хорошей масштабируемости.

В смешанных сценариях "производитель-получатель", когда время обработки очень мало, класс Queue<T>, который имеет внешнюю блокировку, масштабируется лучше, чем класс ConcurrentQueue<T>. Однако, если время обработки имеет значение приблизительно равное 500 FLOPS и выше, то класс ConcurrentQueue<T> масштабируется лучше.

Класс ConcurrentStack иСтек

В чистых сценариях "производитель-получатель", когда время обработки каждого элемента очень мало, класс System.Collections.Concurrent.ConcurrentStack<T> и класс System.Collections.Generic.Stack<T>, который имеет внешнюю блокировку, вероятно будут выполняться одинаково с одним выделенным потоком на добавление и одним выделенным потоком на извлечение. Однако по мере увеличения числа потоков, производительность снижается у обоих типов, так как увеличивается число конфликтных ситуаций, и класс Stack<T> может выполняться лучше, чем класс ConcurrentStack<T>. Если время обработки имеет значение приблизительно равное 500 FLOPS и выше, то оба типа масштабируются примерно одинаково.

В смешанных сценариях "производитель-получатель" класс ConcurrentStack<T> имеет большее ускорение для небольших и больших рабочих нагрузок.

Использование методов PushRange и TryPopRange может значительно снизить время доступа.

Классы ConcurrentDictionary иDictionary

В общем случае, используйте класс System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue> в любом сценарии, в котором выполняется добавление и обновление ключей и значений одновременно из множества потоков. В сценариях, которые включают частные операции обновления и относительно редкие операции чтения, класс ConcurrentDictionary<TKey, TValue>, в общем случае, обеспечивает немного лучшую производительность. В сценариях, которые включают частные операции чтения и относительно редкие операции обновления, класс ConcurrentDictionary<TKey, TValue>, в общем случае, имеет значительно большее ускорение на компьютерах, которые имеют несколько ядер.

В сценариях, которые включают частые обновления, можно увеличить степень параллелизма в классе ConcurrentDictionary<TKey, TValue> и затем провести оценку, чтобы увидеть увеличилась ли производительность на компьютерах, которые имеют несколько ядер. При изменение уровня параллелизма исключите, насколько это возможно, глобальные операции.

Если выполняются только операции чтения ключа или значений, класс Dictionary<TKey, TValue> имеет большее ускорение, так как если словарь не изменяется каким-либо потоком, то синхронизация не требуется.

Класс ConcurrentBag

В чистых сценариях "производитель-получатель" класс System.Collections.Concurrent.ConcurrentBag<T> вероятно будет выполняться более медленно, чем один из типов параллельных коллекций.

В смешанных сценариях "производитель-получатель" класс ConcurrentBag<T> в общем случае имеет большее ускорение и большую масштабируемость, чем все остальные типы параллельных коллекций для небольших и больших рабочих нагрузок.

Класс BlockingCollection

Если требуются семантики границ и блокировок, класс System.Collections.Concurrent.BlockingCollection<T> вероятно будет выполняться быстрее, чем все другие пользовательские реализации. Он также поддерживает гибкую обработку исключений и операций отмены, перечисления.

См. также

Ссылки

System.Collections.Concurrent

Основные понятия

Потокобезопасные коллекции

Параллельное программирование в .NET Framework