Блокировка для ConcurrentDictionary при AddOrUpdate-ing?

Я использую ConcurrentDictioanry<string, HashSet<string>> чтобы получить доступ к некоторым данным во многих потоках.

Я прочитал в этой статье (прокрутите вниз), что метод AddOrUpdate не выполняется в замке, так что это может поставить под угрозу безопасность потока.

Мой код выглядит следующим образом:

//keys and bar are not the concern here
ConcurrentDictioanry<string, HashSet<string>> foo = new ...;
foreach(var key in keys) {
    foo.AddOrUpdate(key, new HashSet<string> { bar }, (key, val) => {
        val.Add(bar);
        return val;
    });
}

Должен ли я приложить AddOrUpdate позвонить в lock заявление, чтобы быть уверенным, что все потокобезопасно?

4 ответа

Решение

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

Если вы собираетесь рассматривать эту коллекцию как поточно-ориентированную, вам действительно нужно, чтобы значения также были поточно-ориентированными. Тебе необходимо ConcurrentSet, в идеале. Теперь это не существует в рамках (если я что-то пропустил), но вы могли бы создать свой собственный ConcurrentSet<T> который использовал ConcurrentDictionary<T, int> (или что угодно TValue вам нравится) в качестве базовой структуры данных. По сути, вы игнорируете значение в словаре и просто рассматриваете наличие ключа как важную часть.

Вам не нужно реализовывать все внутри ISet<T> - просто биты, которые вам действительно нужны.

Затем вы создадите ConcurrentDictionary<string, ConcurrentSet<string>> в коде приложения, и вы в отъезде - нет необходимости в блокировке.

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

ValueFactory может вызываться несколько раз, если несколько потоков одновременно пытаются добавить одно и то же значение ключа, а его нет. Очень низкие шансы, но не ноль. Только один из этих хэш-наборов будет использоваться. Не проблема, создание HashSet не имеет побочных эффектов, которые могут вызвать проблемы с многопоточностью, лишние копии просто собирают мусор.

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

Также еще одна хорошая статья Здесь о Ленивых загрузке делегата добавления.

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

Другие вопросы по тегам