Глава.30 .Гибридные.конструкции.синхронизации.потоков
ref T target, ref Boolean initialized, ref Object syncLock);
public static T EnsureInitialized(ref T target,
ref Boolean initialized, ref Object syncLock, Func valueFactory);
}
Возможность явно указать объект синхронизации в параметре
syncLock
метода
EnsureInitialized
позволяет одной блокировкой защитить сразу несколько полей
и функций инициализации.
Пример использования метода из данного класса:
public static void Main() {
String name = null;
// Так как имя равно null, запускается делегат и инициализирует поле имени
LazyInitializer.EnsureInitialized(ref name, () => "Jeffrey");
Console.WriteLine(name); // Выводится "Jeffrey"
// Так как имя отлично от null, делегат не запускается и имя не меняется
LazyInitializer.EnsureInitialized(ref name, () => "Richter");
Console.WriteLine(name); // Снова выводится "Jeffrey"
}
Паттерн условной переменной
Предположим, что некий поток выполняет код при соблюдении сложного условия.
Можно просто организовать зацикливание этого потока с периодической проверкой
условия. Однако, во-первых, это пустая трата процессорного времени, во-вторых,
невозможно атомарно проверить несколько переменных, входящих в условие. К сча-
стью, существует шаблон программирования, позволяющий потокам эффективно
синхронизировать свои операции на основе сложного условия. Он называется
паттерном условной переменной
(condition variable pattern), а для его применения
можно воспользоваться следующими методами класса
Monitor
:
public static class Monitor {
public static Boolean Wait(Object obj);
public static Boolean Wait(Object obj, Int32 millisecondsTimeout);
public static void Pulse(Object obj);
public static void PulseAll(Object obj);
}
Вот как выглядит данный паттерн:
internal sealed class ConditionVariablePattern {
private readonly Object m_lock = new Object();
private Boolean m_condition = false;
public void Thread1() {
Monitor.Enter(m_lock); // Взаимоисключающая блокировка
881
Паттерн.условной.переменной
// "Атомарная" проверка сложного условия блокирования
while (!m_condition) {
// Если условие не соблюдается, ждем, что его поменяет другой поток
Monitor.Wait(m_lock); // На время снимаем блокировку,
// чтобы другой поток мог ее получить
}
// Условие соблюдено, обрабатываем данные...
Monitor.Exit(m_lock); // Снятие блокировки
}
public void Thread2() {
Monitor.Enter(m_lock); // Взаимоисключающая блокировка
// Обрабатываем данные и изменяем условие...
m_condition = true;
// Monitor.Pulse(m_lock); // Будим одного ожидающего ПОСЛЕ отмены блокировки
Monitor.PulseAll(m_lock); // Будим всех ожидающих ПОСЛЕ отмены блокировки
Monitor.Exit(m_lock); // Снятие блокировки
}
}
В этом коде поток, выполняющий метод
Thread1
, входит в код взаимоисклю-
чающей блокировки и осуществляет проверку условия. В данном случае я всего
лишь проверяю значение поля
Boolean
, но условие может быть сколь угодно
сложным. К примеру, можно взять текущую дату и удостовериться, что сейчас
вторник и март, а заодно проверить, что коллекция состоит из 10 элементов. Если
условие не соблюдается, поток не зацикливается на проверке, так как это было бы
напрасной тратой процессорного времени, а вызывает метод
Wait
. Данный метод
снимает блокировку, чтобы ее мог получить другой поток, и приостанавливает
вызывающий поток.
Метод
Thread2
содержит код, выполняемый вторым потоком. Он вызывает метод
Enter
для блокировки, обрабатывает какие-то данные, меняя при этом состояние
условия, после чего вызывает метод
Pulse(All)
, разблокирующий поток после
вызова метода
Wait
. Метод
Pulse
разблокирует поток, ожидающий дольше всех
(если такие имеются), в то время как метод
PulseAll
разблокирует все ожидающие
потоки (если такие есть). Однако ни один из этих потоков пока не просыпается.
Поток, выполняющий метод
Thread2
, должен вызвать метод
Monitor.Exit
, давая
шанс другому потоку выполнить блокировку. Кроме того, в результате выполнения
метода
PulseAll
потоки разблокируются не одновременно. После освобождения
потока, вызвавшего метод
Wait
, он становится владельцем блокировки, а так как
это взаимоисключающее блокирование, в каждый момент времени им может вла-
деть только один поток. Другие потоки имеют шанс получить право на блокировку
только после того, как текущий владелец вызовет метод
Wait
или
Exit
.
882
Do'stlaringiz bilan baham: |