Я не смог найти достаточно информации о ConcurrentDictionary
типах, поэтому решил спросить об этом здесь.
В настоящее время я использую Dictionary
для хранения всех пользователей, к которым постоянно обращаются несколько потоков (из пула потоков, поэтому точное количество потоков отсутствует), и у него есть синхронизированный доступ.
Недавно я узнал, что в .NET 4.0 есть набор поточно-ориентированных коллекций, и это кажется мне очень приятным. Мне было интересно, какой вариант будет «более эффективным и простым в управлении», поскольку у меня есть выбор между обычным Dictionary
и синхронизированным доступом или ConcurrentDictionary
уже поточно-ориентированным.
Ответы:
На поточно-ориентированную коллекцию и не-поточно-безопасную коллекцию можно взглянуть по-другому.
Рассмотрим магазин без клерка, кроме как на кассе. Если люди не будут действовать ответственно, у вас будет масса проблем. Например, предположим, что покупатель достает банку из банки-пирамиды, в то время как клерк строит пирамиду, и весь ад вырвется наружу. Или, что, если два покупателя потянутся за одним и тем же товаром одновременно, кто выиграет? Будет ли драка? Это небезопасная коллекция. Существует множество способов избежать проблем, но все они требуют какой-то блокировки или, скорее, явного доступа в той или иной форме.
С другой стороны, представьте себе магазин с клерком за столом, и вы можете делать покупки только через него. Вы встаете в очередь и просите у него предмет, он возвращает его вам, и вы выходите из очереди. Если вам нужно несколько предметов, вы можете забрать столько предметов на каждом пути туда и обратно, сколько вы можете вспомнить, но вы должны быть осторожны, чтобы не задевать продавца, это разозлит других клиентов в очереди за вами.
Теперь рассмотрим это. Что, если в магазине с одним продавцом вы дойдете до конца очереди и спросите продавца: «У вас есть туалетная бумага?», И он скажет «Да», а затем вы скажете: «Хорошо, я» Я свяжусь с вами, когда узнаю, сколько мне нужно », тогда, когда вы снова окажетесь в очереди, магазин, конечно, можно будет распродать. Этот сценарий не предотвращается потокобезопасной коллекцией.
Потокобезопасная коллекция гарантирует, что ее внутренние структуры данных действительны в любое время, даже если к ней обращаются из нескольких потоков.
Коллекция, не обеспечивающая потокобезопасность, не дает таких гарантий. Например, если вы добавляете что-то в двоичное дерево в одном потоке, в то время как другой поток занят перебалансировкой дерева, нет никакой гарантии, что элемент будет добавлен, или даже если дерево все еще будет действительным после этого, оно может быть повреждено безнадежно.
Однако потокобезопасная коллекция не гарантирует, что все последовательные операции в потоке работают с одним и тем же «снимком» его внутренней структуры данных, что означает, что если у вас есть такой код:
вы можете получить исключение NullReferenceException, потому что между
tree.Count
иtree.First()
другой поток очистил оставшиеся узлы в дереве, что означаетFirst()
возвратnull
.Для этого сценария вам нужно либо проверить, есть ли у рассматриваемой коллекции безопасный способ получить то, что вы хотите, возможно, вам нужно переписать приведенный выше код или вам может потребоваться заблокировать.
источник
Dictionary
и обработкой блокировки самостоятельно по сравнению с использованиемConcurrentDictionary
типа, встроенного в .NET 4+. На самом деле я немного озадачен, что это было принято.Вам по-прежнему нужно быть очень осторожным при использовании поточно-ориентированных коллекций, потому что поточно-ориентированные коллекции не означают, что вы можете игнорировать все проблемы с потоками. Когда коллекция объявляет себя поточно-ориентированной, это обычно означает, что она остается в согласованном состоянии, даже когда несколько потоков одновременно читают и записывают. Но это не означает, что один поток увидит «логическую» последовательность результатов, если вызовет несколько методов.
Например, если вы сначала проверяете, существует ли ключ, а затем получаете значение, соответствующее ключу, этот ключ может больше не существовать даже с версией ConcurrentDictionary (потому что другой поток мог удалить ключ). В этом случае вам все равно нужно использовать блокировку (или лучше: объединить два вызова с помощью TryGetValue ).
Так что используйте их, но не думайте, что это дает вам возможность игнорировать все проблемы параллелизма. Вам все равно нужно быть осторожным.
источник
TryGetValue
всегда был частьюDictionary
и всегда был рекомендуемым подходом. Важными "параллельными" методами, которыеConcurrentDictionary
вводят, являютсяAddOrUpdate
иGetOrAdd
. Итак, хороший ответ, но можно было бы выбрать лучший пример.Внутри ConcurrentDictionary используется отдельная блокировка для каждого хеш-сегмента. Пока вы используете только Add / TryGetValue и подобные методы, которые работают с отдельными записями, словарь будет работать как почти свободная от блокировки структура данных с соответствующим преимуществом производительности. OTOH методы перечисления (включая свойство Count) блокируют сразу все сегменты и, следовательно, хуже синхронизированного словаря с точки зрения производительности.
Я бы сказал, просто используйте ConcurrentDictionary.
источник
Я думаю, что метод ConcurrentDictionary.GetOrAdd - это именно то, что нужно большинству многопоточных сценариев.
источник
Вы видели реактивные расширения для .Net 3.5sp1? По словам Джона Скита, они сделали резервную копию пакета параллельных расширений и параллельных структур данных для .Net3.5 sp1.
Существует набор примеров для .Net 4 Beta 2, в котором довольно подробно описано, как использовать их параллельные расширения.
Я только что провел последнюю неделю, тестируя ConcurrentDictionary, используя 32 потока для выполнения ввода-вывода. Кажется, он работает так, как рекламируется, что указывает на то, что в него было вложено огромное количество испытаний.
Изменить : .NET 4 ConcurrentDictionary и шаблоны.
Microsoft выпустила PDF-файл под названием Patterns of Paralell Programming. Его действительно стоит загрузить, поскольку в нем очень подробно описаны правильные шаблоны для использования параллельных расширений .Net 4 и анти-шаблоны, которых следует избегать. Вот.
источник
В основном вы хотите использовать новый ConcurrentDictionary. Прямо из коробки вам нужно писать меньше кода, чтобы сделать программы потокобезопасными.
источник
Это
ConcurrentDictionary
отличный вариант, если он удовлетворяет все ваши потребности в безопасности потоков. Если это не так, другими словами, вы делаете что-то немного сложное, нормальныйDictionary
+lock
может быть лучшим вариантом. Например, предположим, что вы добавляете несколько заказов в словарь и хотите постоянно обновлять общую сумму заказов. Вы можете написать такой код:Этот код не является потокобезопасным. Несколько потоков, обновляющих
_totalAmount
поле, могут оставить его в поврежденном состоянии. Так что вы можете попытаться защитить его с помощьюlock
:Этот код «безопаснее», но все же не потокобезопасен. Нет никакой гарантии, что
_totalAmount
это соответствует записям в словаре. Другой поток может попытаться прочитать эти значения, чтобы обновить элемент пользовательского интерфейса:totalAmount
Не может соответствовать кол - ву заказов в словаре. Отображаемая статистика может быть неверной. На этом этапе вы поймете, что вам необходимо расширитьlock
защиту, включив обновление словаря:Этот код совершенно безопасен, но все преимущества использования a
ConcurrentDictionary
.Dictionary
, потому что внутренняя блокировка внутриConcurrentDictionary
теперь расточительна и избыточна.TryAdd
?,AddOrUpdate
?).Итак, мой совет: начните с
Dictionary
+lock
и оставьте вариант обновления позже до AConcurrentDictionary
в качестве оптимизации производительности, если этот вариант действительно жизнеспособен. Во многих случаях это не так.источник
Мы использовали ConcurrentDictionary для кэшированной коллекции, которая повторно заполняется каждые 1 час, а затем читается несколькими клиентскими потоками, аналогично решению для вопроса « Потокобезопасен ли этот пример?вопрос.
Мы обнаружили, что изменение его на ReadOnlyDictionary улучшило общую производительность.
источник