У меня есть следующий класс.
class Test{
public HashSet<string> Data = new HashSet<string>();
}
Мне нужно изменить поле «Данные» из разных потоков, поэтому я хотел бы высказать некоторые мнения о моей текущей поточно-безопасной реализации.
class Test{
public HashSet<string> Data = new HashSet<string>();
public void Add(string Val){
lock(Data) Data.Add(Val);
}
public void Remove(string Val){
lock(Data) Data.Remove(Val);
}
}
Есть ли лучшее решение, чтобы перейти непосредственно к полю и защитить его от одновременного доступа несколькими потоками?
System.Collections.Concurrent
ReaderWriterLock
будет полезен (эффективен), когда несколько читателей и один писатель. Мы должны знать, так ли это для ОПОтветы:
Ваша реализация верна. К сожалению, .NET Framework не предоставляет встроенный тип одновременного хэш-набора. Однако есть некоторые обходные пути.
ConcurrentDictionary (рекомендуется)
Первый - использовать класс
ConcurrentDictionary<TKey, TValue>
в пространстве именSystem.Collections.Concurrent
. В этом случае значение не имеет смысла, поэтому мы можем использовать простоеbyte
(1 байт в памяти).Это рекомендуемый вариант, так как тип является потокобезопасным и предоставляет вам те же преимущества, что
HashSet<T>
и ключ кроме, а значение - это разные объекты.Источник: Социальный MSDN
ConcurrentBag
Если вы не возражаете против повторяющихся записей, вы можете использовать класс
ConcurrentBag<T>
в том же пространстве имен предыдущего класса.Само-реализация
Наконец, как и вы, вы можете реализовать свой собственный тип данных, используя блокировку или другие способы, которые .NET предоставляет вам для обеспечения безопасности потоков. Вот отличный пример: Как реализовать ConcurrentHashSet в .Net
Единственным недостатком этого решения является то, что тип
HashSet<T>
официально не имеет одновременного доступа даже для операций чтения.Я цитирую код связанного поста (первоначально написанный Беном Мошером ).
РЕДАКТИРОВАТЬ: Переместите методы блокировки входа за пределы
try
блоков, так как они могут вызвать исключение и выполнить инструкции, содержащиеся вfinally
блоках.источник
null
ссылка (для ссылки требуется 4 байта в 32-битной среде выполнения и 8 байтов в 64-битной среде выполнения). Следовательно, использованиеbyte
пустой структуры или аналогичного может уменьшить объем занимаемой памяти (или не может быть, если среда выполнения выравнивает данные по собственным границам памяти для более быстрого доступа).Вместо того, чтобы обернуть
ConcurrentDictionary
или заблокировать,HashSet
я создал фактическоеConcurrentHashSet
основанное наConcurrentDictionary
.Эта реализация поддерживает базовые операции для каждого элемента без
HashSet
операций set, поскольку они имеют меньший смысл в параллельных сценариях IMO:Выход: 2
Вы можете получить его от NuGet здесь и посмотреть источник на GitHub здесь .
источник
ISet<T>
интерфейс bo на самом деле совпадает сHashSet<T>
семантикой?Overlaps
например, нужно будет либо заблокировать экземпляр во время его выполнения, либо предоставить ответ, который уже может быть неправильным. Оба варианта плохие IMO (и могут быть добавлены внешне потребителями).Поскольку никто другой не упомянул об этом, я предложу альтернативный подход, который может подходить или не подходить для вашей конкретной цели:
Неизменяемые коллекции Microsoft
Из сообщения в блоге команды MS позади:
Эти коллекции включают ImmutableHashSet <T> и ImmutableList <T> .
Производительность
Поскольку неизменяемые коллекции используют древовидные структуры данных для обеспечения структурного совместного использования, их характеристики производительности отличаются от изменчивых коллекций. При сравнении с изменяемой блокировкой коллекции результаты будут зависеть от конкуренции за блокировку и шаблонов доступа. Однако взято из другого поста в блоге об неизменных коллекциях:
Другими словами, во многих случаях разница не будет заметна, и вы должны пойти на более простой выбор - который для параллельных наборов будет использовать
ImmutableHashSet<T>
, поскольку у вас нет существующей реализации изменяемой блокировки! :-)источник
ImmutableHashSet<T>
не очень помогает, если вы собираетесь обновить общее состояние из нескольких потоков, или я что-то здесь упустил?ImmutableInterlocked.Update
Кажется, недостающее звено. Спасибо!Сложность создания
ISet<T>
параллелизма заключается в том, что методы множества (объединение, пересечение, разность) по своей природе являются итеративными. По крайней мере, вам нужно перебрать все n членов одного из наборов, участвующих в операции, при этом блокируя оба набора.Вы теряете преимущества,
ConcurrentDictionary<T,byte>
когда вам приходится блокировать весь набор во время итерации. Без блокировки эти операции не являются потокобезопасными.Учитывая дополнительные накладные расходы
ConcurrentDictionary<T,byte>
, вероятно, разумнее всего использовать более легкий весHashSet<T>
и просто окружить все в замках.Если вам не нужны операции set, используйте
ConcurrentDictionary<T,byte>
и просто используйтеdefault(byte)
в качестве значения при добавлении ключей.источник
Я предпочитаю полные решения, поэтому я сделал это: имейте в виду, что мой счетчик реализован по-другому, потому что я не понимаю, почему нужно запретить читать хэш-набор при попытке подсчета его значений.
@ Zen, спасибо, что начали.
источник
EnterWriteLock
, почему вообщеEnterReadLock
существует? Не может ли блокировка чтения использоваться для таких методов, какContains
?