Я использовал этот шаблон в нескольких проектах, (это отрезало кода, от CodeCampServer), я понимаю то, что он делает, но я действительно интересен в объяснении об этом шаблоне. Конкретно:
_dependenciesRegistered
.lock (Lock){}
.Спасибо.
public class DependencyRegistrarModule : IHttpModule
{
private static bool _dependenciesRegistered;
private static readonly object Lock = new object();
public void Init(HttpApplication context)
{
context.BeginRequest += context_BeginRequest;
}
public void Dispose() { }
private static void context_BeginRequest(object sender, EventArgs e)
{
EnsureDependenciesRegistered();
}
private static void EnsureDependenciesRegistered()
{
if (!_dependenciesRegistered)
{
lock (Lock)
{
if (!_dependenciesRegistered)
{
new DependencyRegistrar().ConfigureOnStartup();
_dependenciesRegistered = true;
}
}
}
}
}
Это шаблон блокировки с двойной проверкой.
Оператор lock
гарантирует, что код внутри блока не будет выполняться в двух потоках одновременно.
Поскольку оператор lock
несколько дороговат, перед входом в блокировку код проверяет, был ли он уже инициализирован.
Однако, поскольку другой поток мог инициализировать его сразу после внешней проверки, необходимо снова проверить его внутри блокировки.
Блокировка предотвращает запуск ConfigureOnStartup () двумя потоками. Между if (! _DependenciesRegistered) и точкой, в которой ConfigureOnStartup () устанавливает _dependenciesRegistered = true, другой поток может проверить, зарегистрирован ли он. Другими словами:
Двойная проверка нужна потому, что два потока могут одновременно нажать EnsureDependenciesRegistered
, оба обнаружат, что он не зарегистрирован, и таким образом оба попытаются получить блокировку.
lock(Lock)
по сути является разновидностью мьютекса; только один поток может иметь блокировку - другой должен ждать, пока блокировка не будет освобождена (в конце оператора lock(...) {...}
).
Таким образом, в этом сценарии поток может (хотя это маловероятно) быть вторым потоком в блокировке
- поэтому каждый должен дважды проверить, был ли он вторым, а работа уже сделана.
Это вопрос производительности.
Первоначальный тест позволяет быстро выйти из строя, если работа уже сделана. На этом этапе он выполняет потенциально дорогостоящую блокировку, но он должен проверить ее снова, поскольку другой поток уже мог ее зарегистрировать.
Схема блокировки с двойной проверкой примерно выглядит так:
у вас есть операция, которую вы хотите условно выполнить один раз
if (needsToDoSomething) {
DoSomething();
needsToDoSomething = false;
}
, однако, если вы работаете в двух потоках, оба потоки могут проверить флаг и выполнить действие, прежде чем они оба установят для флага значение false. Следовательно, вы добавляете блокировку.
lock (Lock) {
if (needsToDoSomething) {
DoSomething();
needsToDoSomething = false;
}
}
однако взятие блокировки каждый раз, когда вы запускаете этот код, может быть медленным, поэтому вы решаете, давайте только попробуем снять блокировку, когда нам это действительно нужно.
if (needsToDoSomething)
lock (Lock) {
if (needsToDoSomething) {
DoSomething();
needsToDoSomething = false;
}
}
Вы не можете удалить внутреннюю проверку, потому что снова возникает проблема, заключающаяся в том, что любая проверка, выполненная вне блокировки, может дважды оказаться истинной в двух разных потоках.