Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы ОСиСП.doc
Скачиваний:
68
Добавлен:
11.05.2015
Размер:
1.78 Mб
Скачать

26. Мониторы в среде .Net. Ожидание выполнения условий с помощью методов Wait и Pulse. Класс Monitor и блоки синхронизации

В Win32 API структура CRITICAL_SECTION и связанные с ней функции предлагают самый быстрый и эффективный способ синхронизации потоков для взаимоисключающего доступа к общему ресурсу, когда все потоки работают в одном процессе. Взаимоисключающий доступ к общему ресурсу нескольких потоков — это, наверное, самая распространенная форма синхронизации потоков. CLR не предоставляет структуру CRITICAL_SECTION, но предлагает похожий механизм, позволяющий организовать взаимоисключающий доступ к ресурсу в наборе потоков одного процесса. Используя этот механизм, задействуют класс System.Threading.Monitor и блоки синхронизации.

«Отличная» идея

CLR специалисты Microsoft решили создать механизм, который позволил бы разработчикам просто и без усилий синхронизировать доступ к состоянию объекта.

Основная идея такова: с каждым объектом в куче связана структура данных (аналогично структуре CRITICAL SECTION в Win32), которую можно использовать как блокировку синхронизации. В библиотеке FCL (Framework Class Library) есть методы, позволяющие получить ссылку на объект. Эти методы должны использовать структуру данных объекта для установки и освобождения блокировки синхронизации потока.

В Win32 реализующий эту идею неуправляемый класс C++ будет выглядеть примерно так:

class SomeType

{

private:

// Закрытое поле CRITICAL_SECTION, связанное с объектом.

CRITICAL_SECTION m_csObject;

public:

SomeType()

{

// Конструктор инициализирует поле CRITICAL_SECTION объекта.

InitializeCriticalSection(&m_csObject);

}

~SomeType()

{

// Деструктор удаляет поле CRITICAL_SECTION объекта.

DeleteCriticalSection(&m_csObject);

}

void SomeMethod()

{

// Мы используем поле CRITICAL_SECTION объекта для синхронизации

// доступа к объекту нескольких потоков.

EnterCriticalSection(&m_csObject);

// Здесь располагается код, исполняемый безопасно в отношении потоков.

LeaveCriticalSection(&m_csObject);

}

void AnotherMethod()

{

// Мы используем поле CRITICAL_SECTION объекта для синхронизации

// доступа к объекту нескольких потоков.

EnterCriticalSection(&m_csObject);

// Здесь располагается исполняемый безопасный код...

LeaveCriticalSection(&m_csObject);

}

};

В сущности, CLR предоставляет для каждого объекта отдельное поле по типу поля CRITICAL_SECTION и берет на себя задачи инициализации и удаления этого поля. Все, что остается сделать разработчику, — написать код для ввода поля в каждом методе, которому требуется синхронизация потоков.

Реализация «отличной» идеи

Сопоставление поля CRITICAL_SECTION (которое занимает примерно 24 байта в 32-разрядных системах и около 40 байт в 64-разрядных) с каждым объектом в куче довольно расточительно, особенно если для большинства объектов никогда не требуется безопасный доступ. Чтобы снизить расходование памяти, команда разработчиков CLR использует более эффективный способ предоставления только что описанной функциональности. Вот как это работает: при инициализации CLR выделяется память под массив блоков синхронизации. Блок синхронизации — это участок памяти, который можно связать с объектом. Каждый блок синхронизации содержит те же поля, что и Win32-структура CRITICAL_SECTION.

При создании объекта в куче, с ним связываются два дополнительных служебных поля. Первое служебное поле, указатель на объект-тип, содержит адрес памяти, по которому находится объект-тип типа. Второе служебное поле, индекс блока синхронизации, содержит целочисленный индекс блока в массиве блоков синхронизации.

При конструировании объекта индекс блока синхронизации получает отрицательное значение, указывающее, что нет блока синхронизации, поставленного в соответствие объекту. Затем, когда вызывается метод для входа в блок синхронизации объекта, CLR находит в массиве свободный блок синхронизации и задает индексу блока синхронизации объекта значение, соответствующее найденному блоку. Иначе говоря, блоки синхронизации связываются с объектами прямо во время выполнения. После того как все потоки освобождают блок синхронизации объекта, индекс блока синхронизации опять получает отрицательное значение, чтобы считаться свободным, и может связываться с другим объектом. На рис. 24-1 этот процесс показан наглядно.

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

Рис. 24-1. Индекс блока синхронизации объектов в куче (включая объекты типов) может ссылаться на запись в массиве блоков синхронизации CLR