Глава.29 .Примитивные.конструкции.синхронизации.потоков
ReleaseMutex
рекурсивный счетчик уменьшается на единицу. И только после того,
как его значение достигнет 0, владельцем мьютекса может стать другой поток.
Большинство пользователей не в восторге от этой дополнительной логики. Про-
блема в том, что эти «возможности» имеют свою цену. Объекту
Mutex
требуется
дополнительная память для хранения идентификатора потока и рекурсивного
счетчика. И главное, код объекта
Mutex
должен управлять этой информацией, что
тормозит блокировку. Если приложению понадобятся эти дополнительные возмож-
ности, его код сможет реализовать их самостоятельно, не встраивая в объект
Mutex
.
Поэтому многие разработчики стараются обходиться без мьютексов.
Обычно рекурсивное блокирование имеет место, если запертый метод вызывает
другой метод, также требующий блокирования. Это демонстрирует следующий
код:
internal class SomeClass : IDisposable {
private readonly Mutex m_lock = new Mutex();
public void Method1() {
m_lock.WaitOne();
// Делаем что-то...
Method2(); // Метод Method2, рекурсивно получающий право на блокировку
m_lock.ReleaseMutex();
}
public void Method2() {
m_lock.WaitOne();
// Делаем что-то...
m_lock.ReleaseMutex();
}
public void Dispose() { m_lock.Dispose(); }
}
В приведенном фрагменте код, использующий объект
SomeClass
, может вы-
звать метод
Method1
, получающий объект
Mutex
. Этот код выполняет какую-то
безопасную в отношении потоков операцию, а затем вызывает метод
Method2
, также
выполняющий какую-то безопасную в отношении потоков операцию. Благодаря
поддержке рекурсии объектом
Mutex
поток сначала дважды блокируется, а потом
дважды разблокируется, и только после этого мьютекс может перейти к новому по-
току. Если бы класс
SomeClass
использовал вместо мьютекса объект
AutoResetEvent
,
при вызове метода
WaitOne
поток был бы заблокирован.
Рекурсивное блокирование можно легко организовать при помощи объекта
AutoResetEvent
:
internal sealed class RecursiveAutoResetEvent : IDisposable {
private AutoResetEvent m_lock = new AutoResetEvent(true);
private Int32 m_owningThreadId = 0;
private Int32 m_recursionCount = 0;
853
Конструкции.режима.ядра
public void Enter() {
// Получаем идентификатор вызывающего потока
Int32 currentThreadId = Thread.CurrentThread.ManagedThreadId;
// Если вызывающий поток блокируется,
// увеличиваем рекурсивный счетчик
if (m_owningThreadId == currentThreadId) {
m_recursionCount++;
return;
}
// Вызывающий поток не имеет блокировки, ожидаем
m_lock.WaitOne();
// Теперь вызывающий поток блокируется, инициализируем
// идентификатор этого потока и рекурсивный счетчик
m_owningThreadId = currentThreadId;
m_recursionCount = 1;
}
public void Leave() {
// Если вызывающий поток не является владельцем блокировки,
// произошла ошибка
if (m_owningThreadId != Thread.CurrentThread.ManagedThreadId)
throw new InvalidOperationException();
// Вычитаем единицу из рекурсивного счетчика
if (--m_recursionCount == 0) {
// Если рекурсивный счетчик равен 0,
// ни один поток не владеет блокировкой
m_owningThreadId = 0;
m_lock.Set(); // Пробуждаем один ожидающий поток (если такие есть)
}
}
public void Dispose() { m_lock.Dispose(); }
}
Хотя поведение класса
RecursiveAutoResetEvent
идентично классу
Mutex
, объ-
ект
RecursiveAutoResetEvent
при попытке потока получить право на рекурсивное
блокирование будет иметь значительно лучшую производительность, так как про-
цедуры отслеживания потока-владельца и рекурсии теперь находятся в управляемом
коде. Поток осуществляет переход в ядро Windows только при первом получении
объекта
AutoResetEvent
или при окончательной передаче его другому потоку.
Do'stlaringiz bilan baham: |