Позвольте мне сначала убедиться, что я понимаю предложение. Проблема заключается в том, что у нас есть некоторый ресурс, совместно используемый несколькими потоками, назовите его database
, и он допускает две операции: Read(Context)
и Write(Context)
. Предложение состоит в том, чтобы иметь гранулярность блокировки, основанную на свойстве контекста. То есть:
void MyRead(Context c)
{
lock(c.P) { database.Read(c); }
}
void MyWrite(Context c)
{
lock(c.P) { database.Write(c); }
}
Так что теперь, если у нас есть вызов MyRead, где свойство context имеет значение X, и вызов MyWrite, где свойство context имеет значение Y, и эти два вызова участвуют в двух разных потоках, они не сериализовано. Однако, если у нас есть, скажем, два вызова MyWrite и вызов MyRead, и во всех из них свойство context имеет значение Z, эти вызовы сериализуются .
Возможно ли возможно ? Да. Это не делает это хорошей идеей. Как реализовано выше, это плохая идея, и вы не должны делать это.
Поучительно узнать, почему это плохая идея.
Во-первых, это просто не работает, если свойство имеет тип значения, например целое число. Вы можете подумать, что мой контекст - это идентификационный номер, это целое число, и я хочу сериализовать все обращения к базе данных, используя идентификационный номер 123, и сериализовать все обращения, используя идентификационный номер 345, но не сериализовать эти обращения в отношении каждого. Другой. Блокировки работают только для ссылочных типов , а , заключая в бокс тип значения, всегда дает вам только что выделенное поле , поэтому блокировка никогда не будет оспорена, даже если идентификаторы были тот же самый. Это было бы полностью сломано.
Во-вторых, он плохо работает, если свойство является строкой. Замки логически «сравниваются» по эталону , а не значению . С целыми числами в штучной упаковке вы всегда получаете разные ссылки. Со строками вы иногда получаете разные ссылки! (Из-за непоследовательного применения interning .) Вы можете оказаться в ситуации, когда вы блокируете «ABC», а иногда ожидает другую блокировку на «ABC», а иногда нет. !
Но нарушается фундаментальное правило: вы никогда не должны блокировать объект, если только этот объект не был специально разработан как объект блокировки, а тот же код, который контролирует доступ к заблокированному ресурсу, контролирует доступ к объект блокировки .
Проблема здесь не "локальная" для блокировки, а скорее глобальная. Предположим, что ваша собственность Frob
, где Frob
является ссылочным типом. Вы не знаете, блокируется ли какой-либо другой код в вашем процессе тем же Frob
, и, следовательно, вы не знаете, какие ограничения порядка блокировки необходимы для предотвращения взаимных блокировок. Является ли программа блокированной или нет, является глобальным свойством программы. Точно так же, как вы можете построить пустотелый дом из сплошных кирпичей, вы можете создать взаимоблокирующую программу из набора замков, которые индивидуально верны. Убедившись, что каждая блокировка снята только с частного объекта, которым вы управляете , вы гарантируете, что никто больше никогда не блокирует один из ваших объектов, и поэтому анализ того, содержит ли ваша программа взаимоблокировку, становится проще.
Обратите внимание, что я сказал "проще", а не "просто". Это уменьшает его до , почти невозможно получить правильный , с буквально невозможно получить правильный .
Так что, если бы вы были одержимы этим, какой был бы правильный способ сделать это?
Правильным способом было бы реализовать новую службу: поставщик объектов блокировки . LockProvider<T>
должен уметь хеш и сравнивать на равенство два T
с. Служба, которую он предоставляет: вы сообщаете ему, что хотите заблокировать объект для определенного значения T
, и он возвращает вам канонический объект блокировки для этого T
. Когда вы закончите, вы говорите, что все готово. Поставщик ведет подсчет ссылок того, сколько раз он выдал объект блокировки и сколько раз он получил его, и удаляет его из своего словаря, когда счетчик обнуляется, чтобы у нас не было утечки памяти.
Очевидно, что поставщик блокировок должен быть ориентирован на многопотоковое исполнение и требовать крайне низкого уровня конкуренции, поскольку это механизм, разработанный для предотвращения конфликтов , поэтому лучше не вызывать никакого! Если это путь, по которому вы собираетесь идти, вам понадобится эксперт по потокам C # для разработки и реализации этого объекта . Очень легко ошибиться. Как я отметил в комментариях к вашему сообщению, вы пытаетесь использовать параллельную очередь в качестве своего рода плохого поставщика блокировки, и это масса ошибок состояния гонки.
Это один из самых сложных кодов, который можно исправить во всех программах .NET. Я был программистом .NET почти 20 лет и реализовывал части компилятора , и я не считаю себя достаточно компетентным, чтобы все правильно понять. Обратитесь за помощью к настоящему эксперту.