← Назад к вопросам

Какие знаешь Concurrent Collections?

1.7 Middle🔥 161 комментариев
#Коллекции и структуры данных

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Concurrent Collections в C#: Коллекции для многопоточной работы

В C# Concurrent Collections представляют специализированные коллекции, оптимизированные для безопасного использования в многопоточной среде без необходимости внешней синхронизации (например, с помощью lock). Они являются частью пространства имен System.Collections.Concurrent и появились в .NET Framework 4.0. Эти коллекции используют различные механизмы синхронизации (тонкие блокировки, раздельные блокировки, атомарные операции) для обеспечения высокой производительности при параллельных операциях.

Основные типы Concurrent Collections

ConcurrentBag<T>

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

ConcurrentBag<int> bag = new ConcurrentBag<int>();
bag.Add(10);
bag.Add(20);
int result;
if (bag.TryTake(out result))
{
    Console.WriteLine($"Извлечено: {result}");
}

ConcurrentQueue<T>

Потокобезопасная реализация очереди FIFO (First-In-First-Out). Использует раздельные блокировки для головы и хвоста для эффективных операций Enqueue и Dequeue.

ConcurrentQueue<string> queue = new ConcurrentQueue<string>();
queue.Enqueue("Task1");
queue.Enqueue("Task2");
string item;
if (queue.TryDequeue(out item))
{
    Console.WriteLine($"Обработано: {item}");
}

ConcurrentStack<T>

Потокобезопасная реализация стека LIFO (Last-In-First-Out). Использует интерлокированные операции (Interlocked) для атомарного управления головой стека.

ConcurrentStack<int> stack = new ConcurrentStack<int>();
stack.Push(1);
stack.Push(2);
int value;
if (stack.TryPop(out value))
{
    Console.WriteLine($"Извлечено из стека: {value}");
}

ConcurrentDictionary<TKey, TValue>

Потокобезопасный словарь. Использует тонкие блокировки (fine-grained locking) на уровне внутренних сегментов (shards), что позволяет множеству потоков одновременно читать и модифицировать разные части словаря. Особенно эффективен при высокой частоте чтения.

ConcurrentDictionary<string, int> dictionary = new ConcurrentDictionary<string, int>();
dictionary.TryAdd("key1", 100);
dictionary.TryUpdate("key1", 200, 100);
int currentValue = dictionary.GetOrAdd("key2", () => 300);

BlockingCollection<T>

Класс-оболочка, предоставляющий блокирующие и ограничивающие возможности для любой коллекции, реализующей IProducerConsumerCollection<T> (например, ConcurrentQueue или ConcurrentStack). Он поддерживает паттерн Producer-Consumer, позволяя потребителям блокироваться при ожидании элементов.

BlockingCollection<int> blockingCollection = new BlockingCollection<int>(new ConcurrentQueue<int>());
// Producer
Task.Run(() => {
    for (int i = 0; i < 10; i++) blockingCollection.Add(i);
    blockingCollection.CompleteAdding();
});
// Consumer
Task.Run(() => {
    foreach (var item in blockingCollection.GetConsumingEnumerable())
    {
        Console.WriteLine($"Consumed: {item}");
    }
});

Ключевые преимущества и особенности

  • Внутренняя синхронизация: Не требуют внешних lock операций для базовых действий (Add, Take, Dequeue, etc.).
  • Высокая производительность в многопоточной среде: За счет оптимизированных алгоритмов (например, раздельные блокировки в ConcurrentQueue).
  • Атомарные операции: Методы типа TryAdd, TryUpdate, TryDequeue гарантируют целостность данных даже при параллельных вызовах.
  • Отсутствие гарантий полной атомарности для сложных операций: Операции типа "добавить, если отсутствует" (GetOrAdd) атомарны, но последовательность нескольких методов не является атомарной единой операцией. Для таких сценариев иногда требуется дополнительная синхронизация.
  • Итераторы предоставляют "моментальный снимок": При обходе коллекции через foreach возвращается snapshot состояния на момент начала итерации, что безопасно, но может не отражать последующие изменения других потоков.
  • Специализация под разные паттерны: ConcurrentBag для смешанных добавлений/извлечений, BlockingCollection для producer-consumer.

Когда использовать Concurrent Collections?

  • Многопоточные приложения, где несколько потоков активно модифицируют одну коллекцию.
  • Задачи типа Producer-Consumer, особенно с BlockingCollection.
  • Высокочитаемые словари с частыми обновлениями (ConcurrentDictionary).
  • Пул задач или очереди обработки (ConcurrentQueue).

Ограничения и альтернативы

  • Не для всех сценариев: Для простых случаев с редкой конкуренцией обычные коллекции с lock могут быть достаточны.
  • Нет атомарных сложных транзакций: Например, "переместить элемент из очереди в словарь" требует координации.
  • Альтернативы: В современных .NET (Core) также можно рассматривать ImmutableCollections (из System.Collections.Immutable) для сценариев, где изменения редки, или использовать каналы (Channels) (System.Threading.Channels) для асинхронных producer-consumer потоков данных.

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

Какие знаешь Concurrent Collections? | PrepBro