Ответ
Конкурентные коллекции — это потокобезопасные структуры данных из пространства имен System.Collections.Concurrent. Они предназначены для безопасного использования в многопоточных сценариях без необходимости ручной синхронизации с помощью lock.
Ключевые особенности:
- Потокобезопасность: Операции добавления, удаления и чтения являются атомарными.
- Оптимизированная блокировка: Используют продвинутые техники (fine-grained locking, lock-free алгоритмы) для минимизации конфликтов между потоками.
- Согласованность: Гарантируют внутреннюю целостность данных, но не гарантируют моментальную видимость изменений для всех потоков (модель слабой согласованности).
Основные типы и их применение:
-
ConcurrentDictionary<TKey, TValue>Потокобезопасный словарь. Идеален для кэшей или общих справочников.var cache = new ConcurrentDictionary<string, Data>(); // GetOrAdd атомарно получает значение или добавляет новое var data = cache.GetOrAdd("key1", key => FetchDataFromDb(key)); -
ConcurrentQueue<T>Реализует модель FIFO (First-In-First-Out). Часто используется для организации очереди задач между потоками (producer-consumer).var taskQueue = new ConcurrentQueue<Action>(); // Поток-производитель taskQueue.Enqueue(() => Console.WriteLine("Task 1")); // Поток-потребитель if (taskQueue.TryDequeue(out Action task)) { task(); } -
ConcurrentBag<T>Неупорядоченная коллекция, оптимизированная для сценариев, где один поток и добавляет, и удаляет элементы (например, пул объектов).
Важные нюансы:
- Составные операции: Методы вроде
if (!dict.ContainsKey(key)) dict.Add(key, value)не являются атомарными даже сConcurrentDictionary. Вместо этого используйте атомарные методы типаTryAdd,AddOrUpdate,GetOrAdd. - Производительность: В однопоточном режиме они медленнее обычных коллекций (
Dictionary,Queue) из-за накладных расходов на синхронизацию. Используйте их только при реальной необходимости в многопоточном доступе. - Перечисления (
GetEnumerator()): Снимок коллекции делается на момент начала перечисления. Изменения, внесенные во время перечисления, в нем не отразятся.
Ответ 18+ 🔞
А, конкурентные коллекции! Ну это ж тема, где без бутылки не разберёшься, если честно. Смотри, в C# есть такие штуки в System.Collections.Concurrent — они созданы специально, чтобы несколько потоков могли в них лазить одновременно, и при этом всё не разъёбывалось в хлам.
В чём прикол, спросишь?
- Потоки не подерутся: Добавил элемент, удалил, прочитал — всё это происходит так, что один поток другому не наступит на мозги. Атомарно, блядь.
- Умные замки: Там внутри не тупой
lockна всю коллекцию, а хитрая система — точечные блокировки или вообще lock-free алгоритмы, чтобы потоки не стояли в очереди как лохи. - Согласованность: Данные внутри не превратятся в кашу, но если один поток что-то запихал, второй может увидеть это не мгновенно. Это называется "слабая согласованность", не пугайся.
Главные герои этой банды:
-
ConcurrentDictionary<TKey, TValue>Это потокобезопасный словарь, ёпта. Представь кэш в памяти, к которому лезут 10 потоков одновременно — вот для этого он и создан.var cache = new ConcurrentDictionary<string, Data>(); // GetOrAdd — это магия: либо значение достанет, либо создаст новое, и всё это без геморроя с синхронизацией var data = cache.GetOrAdd("key1", key => FetchDataFromDb(key)); -
ConcurrentQueue<T>Очередь по принципу "кто первый зашёл, тот первый вышел". Классика для паттерна "производитель-потребитель": один поток задачи вбрасывает, другой — выгребает и выполняет.var taskQueue = new ConcurrentQueue<Action>(); // Поток-поставщик taskQueue.Enqueue(() => Console.WriteLine("Task 1")); // Поток-работяга if (taskQueue.TryDequeue(out Action task)) { task(); // И работает, блядь } -
ConcurrentBag<T>Беспорядочная куча, но в хорошем смысле. Быстрее всего работает, когда один поток и кладёт, и забирает. Типа пула объектов, который свой же поток использует.
Но есть подводные ебеня, конечно:
- Составные операции — пиздец: Не вздумай делать так:
if (!dict.ContainsKey(key)) dict.Add(key, value). Это две отдельные операции, между ними другой поток может влезть и всё просрать. Используй их встроенные атомарные методы:TryAdd,AddOrUpdate,GetOrAdd— они для этого и придуманы, ебать. - Скорость: Если у тебя один поток, то обычный
DictionaryилиQueueбудут быстрее, потому что тут нет накладных расходов на всю эту синхронизационную магию. Не используй их просто так, для красоты. - Перебор (
GetEnumerator()): Когда начинаешь перебирать, коллекция делает снимок себя на этот момент. Если во время перебора что-то изменится — ты этого в текущем переборе не увидишь. Это не баг, это фича, чтобы голова не взорвалась.