Вопрос о блокировке объектов и подклассов - PullRequest
2 голосов
/ 29 июня 2010

Итак, у меня есть базовый класс, который имеет закрытый объект блокировки, например:

class A
{
    private object mLock = new object();

    public virtual void myMethod()
    {
        lock(mLock)
        {
            // CS
        }
    }
}

Этот объект блокировки используется для большинства операций A ... потому что они должны бытьпотокобезопасен.

Теперь, допустим, я наследую от A примерно так:

class B : public A
{
    public override void myMethod()
    {
        lock(???)
        {
            // CS of mymethod here

            // Call base (thread safe alread)
            base.myMethod();
        }
    }
}

Какое соглашение принято для обеспечения безопасности потока B?Должен ли B также иметь закрытый объект блокировки, как A?Что если мне нужно вызвать базовый метод, как описано выше?

Полагаю, мне просто любопытно, каково соглашение о том, чтобы сделать подклассы потокобезопасными, когда базовый класс уже потокобезопасен.Спасибо!

Редактировать: Некоторые спрашивают, что я подразумеваю под "потокобезопасностью" ... Я поясню, добавив, что я пытаюсь достичь безопасности потоков посредством взаимного исключенияТолько один поток за раз должен выполнять код, который может изменить состояние объекта.

Ответы [ 5 ]

4 голосов
/ 29 июня 2010

На самом деле не достаточно информации, чтобы ответить на ваш вопрос, но обо всем по порядку.

Простое использование блоков lock не делает ваш код поточно-ориентированным.

Все, что вы делаете в A, делает невозможным для нескольких потоков одновременно вызывать любую функцию, тело которой вы заключаете в lock(mLock).Безопасность потоков - это широкий термин, и предотвращение одновременных вызовов - это только один аспект (и не всегда то, что вы хотите или нуждаетесь).Если это то, что вам нужно, то вы, очевидно, выбрали правильный подход.Просто будьте уверены в этом.

Во-вторых, то, что вам нужно представить вашему подклассу, не очевидно из приведенного выше кода.У вас есть три сценария:

  1. B может вызывать protected (или internal, если он находится в той же сборке, что и A) функции A, которые не заключены в *Блоки 1020 *
  2. B будут вызывать только те функции на A, которые заключены в блоки lock(mLock) и не предоставляют каких-либо собственных операций, требующих предотвращения одновременных вызовов
  3. B будет вызывать только те функции на A, которые заключены в блоки lock(mLock), а также выполняет собственные операции, требующие предотвращения одновременных вызовов.

, что действительно сводитсяна два несвязанных вопроса:

  1. Будет ли B взаимодействовать с A способом, который необходимо защитить (другими словами, таким образом, что еще не Защищено)?
  2. Будет ли B предоставлять функциональность, которая должна быть защищена, и, если да, должны ли они использовать один и тот же объект блокировки?

Если 1) верно для 2), и они должны использовать один и тот же объект блокировки, тогдавам нужно будет выставить свой блокирующий объект с помощью свойства protected и использовать его во всех ваших блокировках (я бы также предложил использовать его в пределах A для удобства чтения).

Если ни один из них не равен true,тогда не беспокойся об этом.Если 2) верно, но им не нужно использовать один и тот же объект блокировки (другими словами, было бы приемлемо иметь одновременные вызовы A и B), тогда не беспокойтесь о раскрытии объекта блокировки,Помните, что любые вызовы, которые B делает в функцию на A, защищенную lock(mLock), будут блокировать этот объект в любом случае, независимо от каких-либо внешних блокировок.

4 голосов
/ 29 июня 2010

Это зависит на самом деле. Если ваш подкласс использует только безопасные методы из вашего базового класса и не добавляет дополнительных небезопасных состояний, вам не нужно ничего делать (предпочтительно). Если он добавляет какое-то дополнительное состояние, которое не связано с состоянием базового класса, то вы можете создать отдельный объект блокировки в подклассе и использовать его. Если, с другой стороны, вам нужно убедиться, что новое состояние и состояние базового класса изменено каким-то «транзакционным» способом, я бы сделал объект блокировки в базовом классе защищенным и использовал бы это. 1001 *

4 голосов
/ 29 июня 2010

Вы могли бы потенциально выставить блокировку, используемую A:

protected object SyncLock { get { return mLock; } }

Если бы B имел свой собственный объект блокировки, вы могли бы легко получить:

  • Используются различные блокировки, потенциально приводя к условиям гонки.(Это может быть в порядке - если операции, выполняемые во время удержания блокировки B, ортогональны тем, которые происходят во время удержания блокировки A, вы можете просто сойти с рук.)
  • Блокировки извлекаются рекурсивнов разных порядках, что приводит к тупикам.(Опять же, применяется аргумент ортогональности.)

Так как блокировки являются рекурсивными в .NET (к лучшему или худшему), если ваше переопределение блокирует тот же объект, что и реализация A, можно вызвать base.myMethod и рекурсивно получить / снять блокировку.

Сказав все это, я стремлюсь сделать большинство классов не поточно-безопасными или неизменяемыми (только классы, связанные с многопоточностью, нуждаются в многопоточностизнание) и большинство классов не должны быть предназначены для наследования IMO.

2 голосов
/ 29 июня 2010

Что вы подразумеваете под "потокобезопасным"? Если это включает в себя какие-либо идеи о повторном входе - идею о том, что метод может видеть объект только в хорошем состоянии, то есть только один метод может быть запущен одновременно, - тогда блокировка методов может не выполнить то, что вы ожидаете. Например, если ваш объект когда-либо обращается к другому объекту каким-либо образом (событие?), И этот объект вызывает обратный вызов к исходному объекту, внутренний вызов увидит объект в «плохом» состоянии.

По этой причине, когда вы блокируете весь объект, как этот, вы должны быть осторожны с кодом, который выполняется внутри блокировки: «плохой» код, выполняющийся внутри блокировки, может поставить под угрозу правильность вашего кода.

Поскольку подклассы вводят код, который находится вне контроля исходного объекта, он часто рассматривается как опасный способ работы с самоблокирующимися классами, подобными этому, поэтому одно из популярных соглашений - «не делай этого».

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

0 голосов
/ 29 июня 2010

Я подумал, что здесь нужно использовать lock(this) вместо создания экземпляра другого объекта только для блокировки.Это должно работать в обоих случаях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...