Блокировка для 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>>
в коде приложения, и вы уйдете - нет необходимости в блокировке.
Вам нужно будет исправить этот код, он создает много мусора. Вы создаете новый хэш-набор, даже если он не требуется. Используйте другую перегрузку, ту, которая принимает делегат valueFactory . Таким образом, хэш-набор создается только тогда, когда ключ еще не присутствует в словаре.
ValueFactoryvalueFactory может вызываться несколько раз, если несколько потоков одновременно пытаются добавить одно и то же значение ключа , а его нет. Очень низкие шансы, но не нулевые. Только будет использоваться один из этих хэш-наборов. Не проблема, создание хэш-набора не имеет побочных эффектов, которые могли бы вызвать проблемы с потоками, дополнительные копии просто собирают мусор.
В статье говорится, что делегат add не выполняется в блокировке словаря, и что элемент, который вы получаете, может не быть элементом, созданным в этом потоке делегатом add. Это не проблема потокобезопасности; состояние словаря будет согласованным, и все вызывающие объекты получат один и тот же экземпляр, даже если для каждого из них был создан другой экземпляр (и все, кроме одного, будут удалены).
Кажется, что лучшим ответом было бы использовать Lazy, согласно этой статье о методах, которые передаются в делегате.
Также еще одна хорошая статья здесь о ленивой загрузке делегата add.