С# - блокировка приращения в списке лайков словаря

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

Мне нужно по существу передать строку «Имя», а затем каким-то образом увеличить один из элементов в моем массиве int.

Dictionary<string, int> intStats = new Dictionary<string, int>();

Этот словарь хранит всю статистику на основе «Имени», предоставленного в качестве строкового ключа словаря.

И поскольку я использую МНОГО многопоточности, я хочу, чтобы счетчик int был как можно более синхронизирован. Вот почему я пытаюсь использовать Interlocked.Increment(ref intStats[theName]); Но, к сожалению, это не сработает.

Есть ли альтернативы, которые будут работать для моей ситуации?


person Community    schedule 10.03.2018    source источник
comment
Ты можешь lock словарь? Или создайте словарь блокировки Dictionary<string, object> и заблокируйте один и тот же ключ, увеличивая его, чтобы уменьшить количество конфликтов.   -  person Ron Beyer    schedule 10.03.2018
comment
Не храните int непосредственно в словаре. Инкапсулируйте целое число, а также любые атомарные операции над этим целым числом в пользовательском классе/типе. Используйте экземпляры объектов этого типа для значений словаря. Таким образом, вы получите что-то вроде Dictionary<string, IntWithAtomicOperationsClass>   -  person    schedule 10.03.2018
comment
Кроме того, если вы не можете/не можете полностью заполнить словарь начальными значениями перед запуском всех потоков, может быть предпочтительнее использовать ConcurrentDictionary вместо простого словаря. В противном случае вам потребуется вручную синхронизировать доступ к словарю, чтобы избежать одновременного добавления новых значений под одним и тем же ключом.   -  person    schedule 10.03.2018
comment
@RonBeyer В настоящее время я не знаю, как заблокировать такой словарь.   -  person    schedule 10.03.2018
comment
@elgonzo Я понимаю, что вы говорите, но я не знаю, как настроить такой собственный класс объектов int. И нормальный словарь подходит для моей ситуации. Значение по умолчанию будет предварительно определено с помощью ключа и целого числа 0 до того, как оно будет фактически использовано.   -  person    schedule 10.03.2018


Ответы (1)


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

internal sealed class NameCounter
{
  public int GetCount(string Name) { ... }
  public void Increment(string Name) { ... }
}

Итак: какие варианты реализации вы могли бы сделать, учитывая, что это должно быть потокобезопасным?

  • частный Dictionary<string, int> будет работать, но вам придется блокировать словарь при каждом доступе, что может дорого обойтись.

  • частный ConcurrentDictionary<string, int>, но имейте в виду, что вы должны использовать TryUpdate в цикле, чтобы не потерять значения.

  • сделать тип оболочки:


internal sealed class MutableInt
{
  public int Value;
}

Это один из редких случаев, когда вы хотите сделать публичное поле. Теперь сделайте ConcurrentDictionary<string, MutableInt>, а затем InterlockedIncrement публичное поле. Теперь вам не нужно TryUpdate, но здесь все еще есть гонка: если два потока пытаются одновременно добавить одно и то же имя в первый раз, вы должны убедиться, что только один из них выигрывает. Осторожно используйте AddOrUpdate, чтобы не допустить этой гонки.

  • Реализуйте свой собственный параллельный словарь в виде хэш-таблицы, которая индексируется в массив int; InterlockedIncrement на элементы массива. Опять же, вы должны быть чрезвычайно осторожны, когда в систему вводится новое имя, чтобы убедиться, что коллизии хэшей обнаруживаются потокобезопасным способом.

  • Хэшируйте строку в одну из n групп, но на этот раз сегменты являются неизменяемыми словарями. Каждое ведро имеет замок; заблокировать ведро, создать новый словарь из старого, положить обратно в ведро, разблокировать ведро. Если есть конфликт, увеличивайте n, пока он не исчезнет.

person Eric Lippert    schedule 10.03.2018
comment
В итоге я использовал Dictionary<string, StrongBox<int>> stats = new Dictionary<string, StrongBox<int>>() и просто присвоил значения по умолчанию для каждого имени в качестве параметра String. Тогда для рефери я мог бы сделать (ref stats[name].Value). Вы рекомендуете свое решение по сравнению с моим текущим? - person ; 10.03.2018
comment
@ user8549339: StrongBox очень похоже на MutableInt, но использование свойств не позволяет вам использовать InterlockedIncrement. Dictionary очень похож на ConcurrentDictionary, но не является потокобезопасным. - person Brian; 14.03.2018
comment
С моей стороны StrongBox отлично работает с Interlocked.Increment. И поскольку я не добавляю и не удаляю словарь из нескольких потоков, он работает нормально. По сути, обновляется только int. Как вы думаете, мне все еще следует использовать ConcurrentDictionary? - person ; 15.03.2018