Мне удалось реализовать потокобезопасный словарь на C #, унаследовав его от IDictionary и определив частный объект SyncRoot:
public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
private readonly object syncRoot = new object();
private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
public object SyncRoot
{
get { return syncRoot; }
}
public void Add(TKey key, TValue value)
{
lock (syncRoot)
{
d.Add(key, value);
}
}
// more IDictionary members...
}
Затем я блокирую этот объект SyncRoot для всех моих потребителей (несколько потоков):
Пример:
lock (m_MySharedDictionary.SyncRoot)
{
m_MySharedDictionary.Add(...);
}
Мне удалось заставить его работать, но это привело к некрасивому коду. Мой вопрос: есть ли лучший и более элегантный способ реализации поточно-ориентированного словаря?
Ответы:
Как сказал Питер, вы можете инкапсулировать всю потокобезопасность внутри класса. Вам нужно будет быть осторожным с любыми открытыми или добавляемыми событиями, убедившись, что они вызываются вне любых блокировок.
Изменить: документы MSDN указывают, что перечисление по своей сути не является потокобезопасным. Это может быть одной из причин раскрытия объекта синхронизации за пределами вашего класса. Другой способ подхода - предоставить некоторые методы для выполнения действия над всеми членами и заблокировать перечисление членов. Проблема заключается в том, что вы не знаете, вызывает ли действие, переданное этой функции, какой-либо член вашего словаря (что может привести к тупиковой ситуации). Открытие объекта синхронизации позволяет потребителю принимать эти решения и не скрывает тупик внутри вашего класса.
источник
Назван класс .NET 4.0, поддерживающий параллелизм
ConcurrentDictionary
.источник
Попытки внутренней синхронизации почти наверняка будет недостаточно, потому что она находится на слишком низком уровне абстракции. Скажем , вы делаете
Add
иContainsKey
операции по отдельности поточно- следующим образом :Что тогда происходит, когда вы вызываете этот якобы потокобезопасный фрагмент кода из нескольких потоков? Всегда ли будет работать нормально?
Простой ответ - нет. В какой-то момент
Add
метод вызовет исключение, указывающее, что ключ уже существует в словаре. Вы можете спросить, как это может быть с поточно-ориентированным словарем? Просто потому, что каждая операция является потокобезопасной, комбинация двух операций - нет, поскольку другой поток может изменить ее между вашим вызовомContainsKey
иAdd
.Это означает, что для правильного написания этого типа сценария вам понадобится блокировка вне словаря, например
Но теперь, когда вам нужно написать код внешней блокировки, вы смешиваете внутреннюю и внешнюю синхронизацию, что всегда приводит к таким проблемам, как нечеткий код и взаимоблокировки. Так что в конечном итоге вам, вероятно, лучше:
Используйте обычный
Dictionary<TKey, TValue>
и внешнюю синхронизацию, включив в него составные операции, илиНапишите новую потокобезопасную оболочку с другим интерфейсом (т. Е. Нет
IDictionary<T>
), которая объединяет такие операции, какAddIfNotContained
метод, поэтому вам никогда не придется комбинировать операции из него.(Я сам предпочитаю №1)
источник
Вы не должны публиковать свой частный объект блокировки через свойство. Объект блокировки должен существовать в частном порядке с единственной целью - действовать как точка встречи.
Если при использовании стандартной блокировки производительность оказывается низкой, то набор блокировок Wintellect Power Threading может оказаться очень полезным.
источник
Есть несколько проблем с описываемым вами способом реализации.
Лично я нашел, что лучший способ реализовать потокобезопасный класс - это неизменяемость. Это действительно снижает количество проблем, с которыми вы можете столкнуться с безопасностью потоков. Посетите Блог Эрика Липперта для получения более подробной информации.
источник
Вам не нужно блокировать свойство SyncRoot в ваших потребительских объектах. У вас есть блокировка в методах словаря.
Для уточнения: в конечном итоге ваш словарь заблокирован на более длительный период времени, чем необходимо.
В вашем случае происходит следующее:
Скажем, поток A получает блокировку SyncRoot перед вызовом m_mySharedDictionary.Add. Затем поток B пытается получить блокировку, но блокируется. Фактически, все остальные потоки заблокированы. Потоку A разрешено вызывать метод Add. В операторе блокировки в методе Add потоку A разрешается снова получить блокировку, потому что он уже владеет ею. После выхода из контекста блокировки в методе, а затем вне метода, поток A снял все блокировки, позволяя другим потокам продолжить работу.
Вы можете просто разрешить любому потребителю вызывать метод Add, поскольку оператор блокировки в методе Add вашего класса SharedDictionary будет иметь такой же эффект. На данный момент у вас есть избыточная блокировка. Вы могли бы заблокировать SyncRoot только вне одного из методов словаря, если бы вам нужно было выполнить две операции с объектом словаря, которые должны были гарантированно выполняться последовательно.
источник
Просто мысль, почему бы не воссоздать словарь? Если чтение - это множество записей, то блокировка синхронизирует все запросы.
пример
источник
Коллекции и синхронизация
источник