Почему блокировка (это) {& hellip;} плохая?

Документация MSDN гласит, что

public class SomeObject
{
  public void SomeOperation()
  {
    lock(this)
    {
      //Access instance variables
    }
  }
}

является «проблемой, если к экземпляру можно получить открытый доступ». Мне интересно почему? Это потому, что замок будет держаться дольше, чем необходимо? Или есть еще какая-то коварная причина?

456
задан 16.05.2020, 11:35

6 ответов

Это - невоспитанность для использования this в операторах блокировки, потому что это обычно находится вне контроля, кто еще мог бы соединять тот объект.

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

А частное поле обычно является более оптимальным вариантом, поскольку компилятор осуществит ограничения доступа к нему, и это будет инкапсулировать механизм блокировки. Используя this нарушает инкапсуляцию путем представления части реализации блокировки общественности. Также не ясно, что Вы будете получать блокировку на this, если это не было зарегистрировано. Даже тогда доверие документации для предотвращения проблемы является субоптимальным.

Наконец, существует распространенное заблуждение, которое lock(this) на самом деле изменяет объект, передал в качестве параметра, и в некотором роде делает его только для чтения или недоступным. Это ложь . Объект передал в качестве параметра lock, просто служит ключ . Если блокировка уже прикрепляется, что ключ, блокировка не может быть сделана; иначе блокировка позволяется.

Поэтому это плохо для использования строк в качестве ключей lock операторы, так как они являются неизменными и являются общими/доступными через части приложения. Необходимо использовать частную переменную вместо этого, Object, экземпляр сделает приятно.

Выполнение следующие C# кодируют как пример.

public class Person
{
    public int Age { get; set;  }
    public string Name { get; set; }

    public void LockThis()
    {
        lock (this)
        {
            System.Threading.Thread.Sleep(10000);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var nancy = new Person {Name = "Nancy Drew", Age = 15};
        var a = new Thread(nancy.LockThis);
        a.Start();
        var b = new Thread(Timewarp);
        b.Start(nancy);
        Thread.Sleep(10);
        var anotherNancy = new Person { Name = "Nancy Drew", Age = 50 };
        var c = new Thread(NameChange);
        c.Start(anotherNancy);
        a.Join();
        Console.ReadLine();
    }

    static void Timewarp(object subject)
    {
        var person = subject as Person;
        if (person == null) throw new ArgumentNullException("subject");
        // A lock does not make the object read-only.
        lock (person.Name)
        {
            while (person.Age <= 23)
            {
                // There will be a lock on 'person' due to the LockThis method running in another thread
                if (Monitor.TryEnter(person, 10) == false)
                {
                    Console.WriteLine("'this' person is locked!");
                }
                else Monitor.Exit(person);
                person.Age++;
                if(person.Age == 18)
                {
                    // Changing the 'person.Name' value doesn't change the lock...
                    person.Name = "Nancy Smith";
                }
                Console.WriteLine("{0} is {1} years old.", person.Name, person.Age);
            }
        }
    }

    static void NameChange(object subject)
    {
        var person = subject as Person;
        if (person == null) throw new ArgumentNullException("subject");
        // You should avoid locking on strings, since they are immutable.
        if (Monitor.TryEnter(person.Name, 30) == false)
        {
            Console.WriteLine("Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string \"Nancy Drew\".");
        }
        else Monitor.Exit(person.Name);

        if (Monitor.TryEnter("Nancy Drew", 30) == false)
        {
            Console.WriteLine("Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!");
        }
        else Monitor.Exit("Nancy Drew");
        if (Monitor.TryEnter(person.Name, 10000))
        {
            string oldName = person.Name;
            person.Name = "Nancy Callahan";
            Console.WriteLine("Name changed from '{0}' to '{1}'.", oldName, person.Name);
        }
        else Monitor.Exit(person.Name);
    }
}

Консоль произвела

'this' person is locked!
Nancy Drew is 16 years old.
'this' person is locked!
Nancy Drew is 17 years old.
Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string "Nancy Drew".
'this' person is locked!
Nancy Smith is 18 years old.
'this' person is locked!
Nancy Smith is 19 years old.
'this' person is locked!
Nancy Smith is 20 years old.
Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!
'this' person is locked!
Nancy Smith is 21 years old.
'this' person is locked!
Nancy Smith is 22 years old.
'this' person is locked!
Nancy Smith is 23 years old.
'this' person is locked!
Nancy Smith is 24 years old.
Name changed from 'Nancy Drew' to 'Nancy Callahan'.
487
ответ дан 16.05.2020, 11:36
  • 1
    @Onorio: в основном каждый элемент UI является блобом изменяемого состояния, как все свойства как его местоположение, размер, eventhandlers, текст, наборы дочерних элементов управления, или другие свойства могут быть свободно видоизменены. Так это doesn' t отображаются очень чисто на неизменное представление состояния формы. Таким образом, или необходимо перейти через обручи, пытающиеся обновить неизменную форму, указывают каждый раз изменения UI, или Вы заканчиваете тем, что писали обязательный F#. По крайней мере, это было мой опыт, YMMV. – Juliet 03.10.2019, 11:02
  • 2
    (в противоположность 'IEnumerable< T> дополнительные методы), единственный путь к потоку, который хочет, чтобы снимок набора добрался, можно быть должен перечислить его при блокировке всех изменений . Чтобы сделать это, это должно иметь доступ к блокировке, которая получена любым кодом, который изменил бы набор. Отказ представить блокировку может лишить возможности на, например, иметь программу, периодически выполняют асинхронный снимок набора (например, обновить просматривающий набор пользовательский интерфейс). – supercat 16.05.2020, 11:36
  • 3
    Используя стандартную переменную вместо lock(this) стандартный совет; it' s важный, чтобы отметить, что выполнение так будет обычно лишать возможности внешний код, заставляют блокировку, связанную с объектом быть отложенной между вызовами метода. Это может или не может быть хорошей вещью . Существует некоторая опасность в разрешении вне кода содержать блокировку на произвольное время, и классы должны обычно разрабатываться, чтобы сделать такое использование ненужным, но существует not' t всегда практические альтернативы. Как простой пример, если набор не реализует ToArray или ToList собственный метод... – supercat 16.05.2020, 11:37
  • 4
    Как я grok: (1) Nancy находится в thread1 с блокировкой (это). (2) ТА ЖЕ Nancy находится в старении thread2, в то время как все еще привязано thread1 - доказательство, что заблокированный объект не только для чтения. ТАКЖЕ (2a) , в то время как в потоке 2, этот объект Nancy также заблокирован на Имени. (3) Создают РАЗЛИЧНЫЙ объект с тем же Именем . (4) Передача в thread3 & попытайтесь заблокировать с Именем. (большой конец) , НО " строки являются immutable" значение любого объекта, ссылающегося на строку " Nancy Drew" смотрит на буквально тот же строковый экземпляр в памяти. Так object2 can' t получают блокировку на строке, когда object1 заблокирован на том же значении – radarbob 16.05.2020, 11:37

Существует также некоторая хорошая дискуссия об этом здесь: действительно ли это - надлежащее использование взаимного исключения?

2
ответ дан 16.05.2020, 11:37
  • 1
    Одна вещь, отсутствующая в этом решении, делает chmod на создании файла журнала в первый раз, когда это создается. – Cory Engebretson 17.10.2019, 01:02

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

, Чтобы быть ясным, это плохо, потому что некоторый другой блок кода мог использовать экземпляр класса для блокировки, и мог бы препятствовать тому, чтобы код получил своевременную блокировку, или мог создать другие проблемы синхронизации потока. Лучший случай: ничто иное не использует ссылку на Ваш класс для блокировки. Средний случай: что-то использует ссылку на Ваш класс, чтобы сделать блокировки, и он вызывает проблемы производительности. Худший случай: что-то использует ссылку Вашего класса, чтобы сделать блокировки, и он вызывает действительно плохие, действительно тонкие, действительно трудные к отладке проблемы.

1
ответ дан 16.05.2020, 11:40
  • 1
    Я как раз собирался отправить то же решение: P – Alvin Row 17.10.2019, 01:02

... и те же самые аргументы относятся к этой конструкции также:

lock(typeof(SomeObject))
25
ответ дан 16.05.2020, 11:42

Смотрите на Тему MSDN , Синхронизация Потока (Руководство по программированию C#)

Обычно лучше стараться не соединять открытый тип, или на экземплярах объектов вне управления Вашего приложения. Например, заблокируйте (это) может быть проблематично, если к экземпляру можно получить доступ публично, потому что код вне Вашего управления может соединить объект также. Это могло создать ситуации с мертвой блокировкой, где два или больше потока ожидают выпуска того же объекта . Соединение общедоступного типа данных, в противоположность объекту, может вызвать проблемы по той же причине. Соединение литеральных строк особенно опасно, потому что литеральные строки интернируются общеязыковой средой выполнения (CLR). Это означает, что существует один экземпляр любого данного строкового литерала для всей программы, тот же самый объект представляет литерал во всех доменах запущенного приложения на всех потоках. В результате блокировка, помещенная на строку с тем же содержанием где угодно в процессе приложения, блокирует все экземпляры той строки в приложении. В результате лучше блокировать частного или защищенного участника, который не интернируется. Некоторые классы предоставляют участникам специально для блокировки. Тип массива, например, обеспечивает SyncRoot. Много типов набора предоставляют члену SyncRoot также.

42
ответ дан 16.05.2020, 11:44
  • 1
    ^ " питание of" оператор в VB.NET. – Christian Hayter 01.11.2009, 16:07

Поскольку, если люди могут достигнуть Ваш экземпляр объекта (т.е.: Ваш this), указатель, тогда они могут также попытаться заблокировать тот же самый объект. Теперь они не могли бы знать, что Вы соединяетесь this внутренне, таким образом, это может вызвать проблемы (возможно мертвая блокировка)

В дополнение к этому, это - также плохая практика, потому что это блокирует "слишком много"

, Например, у Вас могла бы быть членская переменная List<int>, и единственной вещью, которую на самом деле необходимо заблокировать, является та членская переменная. При блокировке всего объекта в функциях то другие вещи, которые вызывают те функции, будут заблокированы, ожидая блокировки. Если те функции не должны будут получать доступ к списку элементов, Вы будете заставлять другой код ожидать и замедлять Ваше приложение ни по какой причине вообще.

62
ответ дан 16.05.2020, 11:44
  • 1
    @LukeH зависит от чисел you' ре, работающее с, я предполагаю. Я быстро заметил когда мои объекты с высотой 2 ^ 2 weren' t видимый. – user247702 01.03.2012, 15:12
  • 2
    @Orion: That' s более ясный. @Herms: Да, но Вы don' t должен использовать ' this' для достижения той функциональности свойство SyncRoot в списках служит той цели, например, при прояснении, что синхронизация должна быть сделана на том ключе. – Esteban Brenes 16.05.2020, 11:45
  • 3
    Это делает, если другие методы, называемые также, делают блокировку (это). Я верю that' s точка он делал. Заметьте " Если Вы блокируете весь объект в своем functionS"... – Herms 16.05.2020, 11:45
  • 4
    Ре: блокировка " также much": It' s прекрасное уравновешивание, решающее, что заблокировать. Знайте, что взятие блокировки включает операции ЦП очистки кэша и является несколько дорогим. Другими словами: не блокируйте и обновляйте каждое отдельное целое число.:) – Zan Lynx 16.05.2020, 11:45
  • 5
    Последний абзац этого ответа не корректен. Блокировка ни в коем случае не делает объект недоступным или только для чтения. Заблокируйте (это) не препятствует тому, чтобы другой поток назвал или изменил объект, на который это ссылается. – Esteban Brenes 16.05.2020, 11:46

Теги

Похожие вопросы