.NET - Блокировка словаря по сравнению с ConcurrentDictionary

Если мы говорим об «обычном» количестве тегов - скажем, «десятках» тегов, а не «тысячах» - тогда подход удаления / вставки не такая уж плохая идея.

Однако вы можете сделать это в одном утверждении, которое применяет изменения:

with new_tags (product_id, tag_id) as (
  values (1,3),(1,6),(1,9)
), remove_tags as (
  delete from product_tag pt1
  using new_tags nt
  where pt1.product_id = nt.product_id
   and pt1.tag_id <> ALL (select tag_id from new_tags) 
)
insert into product_tag (product_id, tag_id)
select product_id, tag_id
from new_tags
on conflict do nothing;

Выше предполагается, что (product_id,tag_id) определен как первичный ключ в product_tag. [116 ]

Онлайн пример: https://rextester.com/VVL1293

121
задан ulrichb 4 June 2011 в 07:52
поделиться

6 ответов

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

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

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

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

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

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

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

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

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

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

.
137
ответ дан 24 November 2019 в 01:30
поделиться

Эти ConcurrentDictionary большая опция, если она выполняет весь Ваши потребности потокобезопасности. Если это не, другими словами, Вас делают что-либо немного сложное, нормальным Dictionary + lock может быть более оптимальный вариант. Например, позволяет, говорят, что Вы добавляете некоторые заказы в словарь, и Вы хотите держать общую сумму в курсе заказов. Можно написать код как это:

private ConcurrentDictionary<int, Order> _dict;
private Decimal _totalAmount = 0;

while (await enumerator.MoveNextAsync())
{
    Order order = enumerator.Current;
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

Этот код не ориентирован на многопотоковое исполнение. Несколько потоков, обновляющих _totalAmount поле, могут оставить его в поврежденном состоянии. Таким образом, можно попытаться защитить его с lock:

_dict.TryAdd(order.Id, order);
lock (_locker) _totalAmount += order.Amount;

Этот код "более безопасен", но все еще ориентирован на многопотоковое исполнение. Нет никакой гарантии, которая эти _totalAmount согласовывается с записями в словаре. Другой поток может попытаться считать эти значения, обновить элемент UI:

Decimal totalAmount;
lock (_locker) totalAmount = _totalAmount;
UpdateUI(_dict.Count, totalAmount);

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

// Thread A
lock (_locker)
{
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

// Thread B
int ordersCount;
Decimal totalAmount;
lock (_locker)
{
    ordersCount = _dict.Count;
    totalAmount = _totalAmount;
}
UpdateUI(ordersCount, totalAmount);

Этот код совершенно безопасен, но всех преимуществ использования ConcurrentDictionary не стало.

  1. производительность стала хуже, чем использование нормального Dictionary, потому что внутренняя блокировка в эти ConcurrentDictionary теперь расточительна и избыточна.
  2. необходимо рассмотреть весь код для незащищенного использования совместно используемых переменных.
  3. Вы застреваете с использованием неловкого API (TryAdd?, AddOrUpdate?).

, Таким образом, мой совет: запустите с Dictionary + lock и сохраните опцию обновления позже до ConcurrentDictionary как оптимизация производительности, если этот вариант на самом деле жизнеспособен. Во многих случаях это не будет.

1
ответ дан 24 November 2019 в 01:30
поделиться

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

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

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

61
ответ дан 24 November 2019 в 01:30
поделиться

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

4
ответ дан 24 November 2019 в 01:30
поделиться

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

.
15
ответ дан 24 November 2019 в 01:30
поделиться
[

] Видели ли вы [] реактивные расширения [] для .Net 3.5sp1. По словам Джона Скита (Jon Skeet), для .Net 3.5 sp1 они сообщили о пакете параллельных расширений и параллельных структурах данных.[

] [

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

] [

]Я только что провел последнюю неделю, тестируя ConcurrentDictionary, используя 32 потока для выполнения ввода/вывода. Похоже, что он работает так, как рекламируется, что говорит о том, что в него было вложено огромное количество тестов. [

] [

][]Правка []: .NET 4 ConcurrentDictionary и паттерны. [

] [

]Microsoft выпустила pdf под названием Patterns of Paralell Programming. Его стоит скачать, так как в нем очень подробно описаны правильные шаблоны для .Net 4 ConcurrentDictionary и анти-шаблоны, которых следует избегать. []Вот оно.[][

]
13
ответ дан 24 November 2019 в 01:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: