Я использую C# & .NEt 3.5. В чем разница между OptionA и OptionB?
class MyClass
{
private object m_Locker = new object();
private Dicionary<string, object> m_Hash = new Dictionary<string, object>();
public void OptionA()
{
lock(m_Locker){
// Do something with the dictionary
}
}
public void OptionB()
{
lock(m_Hash){
// Do something with the dictionary
}
}
}
Я начинаю баловаться потоками (в первую очередь для создания кеша для многопоточного приложения, НЕ используя класс HttpCache, поскольку он не прикреплен к веб-сайту), и я вижу синтаксис OptionA во многих примерах, которые я см. в Интернете, но я не понимаю, по какой причине, если таковая имеется, это делается по OptionB.





Важно не то, что вы "блокируете", а код, который содержится между блокировкой {...} и который вы предотвращаете от выполнения.
Если один поток устанавливает lock () для любого объекта, это предотвращает получение другими потоками блокировки для того же объекта и, следовательно, предотвращает выполнение вторым потоком кода, заключенного в фигурные скобки.
Вот почему большинство людей просто создают нежелательный объект для блокировки, это не позволяет другим потокам получить блокировку на этом же нежелательном объекте.
Вариант B использует защищаемый объект для создания критической секции. В некоторых случаях это более четко передает намерение. При последовательном использовании он гарантирует, что одновременно будет активна только одна критическая секция для защищаемого объекта:
lock (m_Hash)
{
// Across all threads, I can be in one and only one of these two blocks
// Do something with the dictionary
}
lock (m_Hash)
{
// Across all threads, I can be in one and only one of these two blocks
// Do something with the dictionary
}
Вариант А менее строгий. Он использует вторичный объект, чтобы создать критическую секцию для объекта, который должен быть защищен. Если используется несколько вторичных объектов, возможно, что одновременно будет активным более одной критической секции для защищаемого объекта.
private object m_LockerA = new object();
private object m_LockerB = new object();
lock (m_LockerA)
{
// It's possible this block is active in one thread
// while the block below is active in another
// Do something with the dictionary
}
lock (m_LockerB)
{
// It's possible this block is active in one thread
// while the block above is active in another
// Do something with the dictionary
}
Вариант A эквивалентен варианту B, если вы используете только один вторичный объект. Что касается чтения кода, намерение варианта B более ясное. Если вы защищаете более одного объекта, вариант B не подходит.
наверное нормально использовать OptionB, потому что m_Hash является частным. OptionA более распространен, потому что обычно считается опасным блокировать объект, который не является частным / на который вы передаете ссылки. Может быть трудно или невозможно отследить (во время написания кода), где может оказаться этот экземпляр и, следовательно, кто еще может заблокировать его. (Если вы действительно хотите отправить объект и, возможно, заблокировать его и в других классах, вам, вероятно, следует написать потокобезопасную оболочку вокруг вашего объекта и вместо этого поделиться экземпляром оболочки.)
Ну, это зависит от того, что вы хотите заблокировать (сделать потокобезопасным).
Обычно я бы выбрал OptionB, чтобы обеспечить поточно-безопасный доступ ТОЛЬКО к m_Hash. Если в качестве OptionA я бы использовал тип значения блокировки, который нельзя использовать с блокировкой, или у меня была группа объектов, которые нуждаются в одновременной блокировке, но я не знаю, что заблокировать весь экземпляр с помощью lock(this)
Блокировка используемого вами объекта - это просто вопрос удобства. Внешний объект блокировки может упрощает работу и также необходим, если общий ресурс является частным, например, с коллекцией (в этом случае вы используете объект ICollection.SyncRoot).
Важно понимать, что блокировка (m_Hash) не позволяет НЕТ предотвращать использование хеша другим кодом. Это только предотвращает запуск другого кода, который также использует m_Hash в качестве объекта блокировки.
Одна из причин использовать вариант A заключается в том, что классы, вероятно, будут иметь частные переменные, которые вы будете использовать внутри оператора блокировки. Гораздо проще просто использовать один объект, который вы используете для блокировки доступа ко всем из них, вместо того, чтобы пытаться использовать более тонкие блокировки для блокировки доступа только к тем элементам, которые вам понадобятся. Если вы попытаетесь использовать более мелкозернистый метод, вам, вероятно, придется использовать несколько блокировок в некоторых ситуациях, а затем вам нужно убедиться, что вы всегда принимаете их в одном и том же порядке, чтобы избежать взаимоблокировок.
Еще одна причина использовать вариант A заключается в том, что ссылка на m_Hash может быть доступна за пределами вашего класса. Возможно, у вас есть общедоступное свойство, которое предоставляет к нему доступ, или, может быть, вы объявили его защищенным, и производные классы могут его использовать. В любом случае, когда внешний код имеет ссылку на него, возможно, что внешний код будет использовать его для блокировки. Это также открывает возможность возникновения взаимоблокировок, поскольку у вас нет возможности контролировать или знать, в каком порядке будет выполняться блокировка.
Я думаю, что объем переменной, которую вы «передаете», будет определять объем блокировки. т.е. переменная экземпляра будет относиться к экземпляру класса, тогда как статическая переменная будет относиться ко всему домену приложения.
Глядя на реализацию коллекций (с использованием Reflector), можно заметить, что из шаблона следует, что переменная экземпляра с именем SyncRoot объявляется и используется для всех операций блокировки в отношении экземпляра коллекции.
На самом деле, не рекомендуется блокировать объект, если вы используете его члены.
Джеффри Рихтер написал в своей книге «CLR через C#», что нет гарантии, что класс объекта, который вы используете для синхронизации, не будет использовать lock(this) в своей реализации (это интересно, но Microsoft рекомендовала этот способ синхронизации для некоторых time ... Затем они обнаружили, что это была ошибка), поэтому всегда полезно использовать специальный отдельный объект для синхронизации. Итак, как видите, OptionB не дает гарантии тупиковой ситуации - безопасности.
Итак, OptionA намного безопаснее, чем OptionB.
Фактически вы не отвечаете на вопрос, который касается двух способов использования объекта, кроме this.
Может быть, мой ответ был недостаточно ясным, я его немного отредактировал.
OptionA - это путь сюда, как и во всем вашем коде, при доступе к m_hash вы используете m_Locker, чтобы заблокировать его.
Теперь представьте себе этот случай. Вы фиксируете объект. И этот объект в одной из вызываемых вами функций имеет сегмент кода lock(this). В данном случае это верный неустранимый тупик.
Я не понимаю, как второй и третий абзацы следуют за первым, в котором утверждается, что то, что вы фиксируете, не важно.