Блокировка для 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 4

4 ответа:

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

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

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

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

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

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

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

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

Также еще одна хорошая статья здесь о ленивой загрузке делегата add.