.NET - блокировка словаря и ConcurrentDictionary - PullRequest
116 голосов
/ 23 декабря 2009

Мне не удалось найти достаточно информации о ConcurrentDictionary типах, поэтому я решил спросить об этом здесь.

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

Недавно я обнаружил, что в .NET 4.0 был набор потоковобезопасных коллекций, и это, кажется, очень приятно. Мне было интересно, что будет «более эффективным и простым в управлении» вариантом, так как у меня есть выбор между обычным Dictionary с синхронизированным доступом или ConcurrentDictionary, который уже поточно-ориентирован.

Ссылка на .NET 4.0's ConcurrentDictionary

Ответы [ 7 ]

135 голосов
/ 27 декабря 2009

Потокобезопасная коллекция и не-безопасная коллекция могут рассматриваться по-другому.

Рассмотрим магазин без клерка, кроме как на кассе. У вас масса проблем, если люди не действуют ответственно. Например, скажем, клиент берет банку из банки с пирамидой, в то время как клерк в настоящее время строит пирамиду, и весь ад развалится. Или, что, если два клиента одновременно достанут один и тот же товар, кто выиграет? Будет ли бой? Это не потокобезопасная коллекция. Существует множество способов избежать проблем, но все они требуют какой-то блокировки или, скорее, явного доступа тем или иным способом.

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

Теперь рассмотрим это. В магазине с одним клерком, что если вы дойдете до самого конца очереди и спросите у клерка «У вас есть туалетная бумага», он скажет «Да», а затем вы скажете «Хорошо, я» Я вернусь к вам, когда узнаю, сколько мне нужно », а к тому времени, когда вы вернетесь к началу очереди, магазин, конечно, может быть распродан. Этот сценарий не предотвращается потокобезопасной коллекцией.

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

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

Тем не менее, потокобезопасная коллекция не гарантирует, что все последовательные операции над потоком работают с одним и тем же «снимком» его внутренней структуры данных, что означает, что если у вас есть код, подобный этому:

if (tree.Count > 0)
    Debug.WriteLine(tree.First().ToString());

вы можете получить исключение NullReferenceException, поскольку в промежутке между tree.Count и tree.First() другой поток очистил оставшиеся узлы в дереве, что означает, что First() вернет null.

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

58 голосов
/ 23 декабря 2009

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

Например, если вы сначала проверите, существует ли ключ, а затем получите значение, соответствующее ключу, этот ключ может больше не существовать даже с версией ConcurrentDictionary (поскольку другой поток мог удалить ключ). В этом случае вам все еще нужно использовать блокировку (или лучше: объединить два вызова, используя TryGetValue ).

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

34 голосов
/ 26 декабря 2010

Внутренне ConcurrentDictionary использует отдельную блокировку для каждого блока хеша. Пока вы используете только Add / TryGetValue и подобные методы, которые работают с отдельными записями, словарь будет работать как практически свободная от блокировки структура данных с соответствующим приятным выигрышем в производительности. OTOH методы перечисления (включая свойство Count) блокируют все сегменты сразу и поэтому хуже, чем синхронизированный словарь, с точки зрения производительности.

Я бы сказал, просто используйте ConcurrentDictionary.

14 голосов
/ 05 января 2010

Вы видели Реактивные расширения для .Net 3.5sp1. По словам Джона Скита, они поддержали пакет параллельных расширений и параллельных структур данных для .Net3.5 sp1.

Существует набор примеров для .Net 4 Beta 2, который довольно подробно описывает, как использовать их для параллельных расширений.

Я только что провел последнюю неделю, тестируя ConcurrentDictionary, используя 32 потока для выполнения операций ввода-вывода. Кажется, он работает так, как рекламируется, что указывает на огромное количество испытаний.

Редактировать : .NET 4 ConcurrentDictionary и шаблоны.

Microsoft выпустила PDF-файл под названием «Шаблоны программирования Параллеля». Его действительно стоит скачать, так как в очень хороших деталях описаны правильные шаблоны, которые следует использовать для расширений .Net 4 Concurrent, и анти-шаблоны, которых следует избегать. Вот оно.

14 голосов
/ 27 декабря 2009

Я думаю, что метод ConcurrentDictionary.GetOrAdd - это именно то, что нужно большинству многопоточных сценариев.

5 голосов
/ 23 декабря 2009

По сути, вы хотите использовать новый ConcurrentDictionary. Прямо из коробки вам нужно писать меньше кода, чтобы создавать поточно-ориентированные программы.

1 голос
/ 05 мая 2013

Мы использовали ConcurrentDictionary для кэшированной коллекции, которая обновляется каждые 1 час и затем читается несколькими клиентскими потоками, аналогично решению для Безопасен ли этот пример потока? вопрос.

Мы обнаружили, что изменение его на ReadOnlyDictionary улучшило общую производительность.

...