Последовательный доступ к ресурсам

В программном интерфейсе ОС Windows имеются удобные средства организации последовательного доступа к ресурсам. Это критические секции объекты взаимно исключающего доступа Mutex и блокирующие функции из­менения содержимого переменных.

Критические секции

Критические секции являются наиболее простым средством синхрони­зации задач для последовательного доступа к ресурсам.

Критическая секция создается как структура типа CMTICALSECTION:

CRITICAL_SECTION csWindowPaint;

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

Перед использованием критической секции ее нужно инициализировать при помощи функции InitializeCriticalSection(&csWindowsPaint). Функция InitializeCritical Section имеет только один параметр - адрес структуры типа CRITICALSECTION и не возвращает никакого значения.

Если критическая секция больше не нужна, ее нужно удалить при по­мощи функции DeleteCriticflSection(&csWindowPaint).

Две основные операции, выполняемые над критическими секциями, - это вход в критическую секцию и выход из нее. Первая операция выполняется при помощи функции EnterCrititicalSection(&csWmdowPaint), вторая при по­мощи функции LeaveCriticalSection(&csWindowPaint). Эти функции всегда используются в паре. В качестве единственного параметра функциям необхо­димо передать адрес структуры типа CRITICAL_SECTION, проинициализированные предварительно функциейl InitializeCriticalSection.

Как работают критические секции? Если одна задача вошла в критиче­скую секцию, но еще не вышла из нее, то при попытке других задач войти в ту же самую критическую секцию они будут переведены в состояние ожида­ния. Задачи пробудут в состоянии ожидания до тех пор, пока задача, которая вошла в критическую секцию, не покинет ее. Таким образом, гарантируется го фрагмент кода, заключенный между вызовами функций EnterCrititicalSection, и LeaveCriticalSection, будет выполняться последовательно.

Объекты Mutex

Если необходимо обеспечить последовательное использование ресурсов задачами, созданными в рамках разных процессов, вместо критических сек­ций необходимо использовать объекты синхронизации Mutex. Так же как объект-событие, объект Mutex может находиться в отмеченном или неотме­ченном состоянии. Когда какая-либо задача, принадлежащая любому процес­су, становится владельцем объекта Mutex, последний переключается в неот­меченное состояние. Если задача «отказывается» от владения объектом Mutex, его состояние становится отмеченным. В каждый момент времени только одна задача, может владеть объектом Mutex, это обеспечивает возмож­ность организации последовательного доступа к ресурсам. Все остальные за­дачи, для того чтобы завладеть объектом, который уже захвачен, должны ждать, например, с помощью функции WaitForSingleobject.

Для создания объекта Mutex используется функция CreateMutex. Для то­го чтобы объект Mutex был доступен задачам, принадлежащих другим про­цессам, при создании объекта необходимо указать его имя:

HANDLE CreateMutex(

LPSECURITY^ATTRIBUTES lpMutexAttributes, // атрибуты защиты BOOL blnitialOwner, // начальное состояние

LPCTSTR IpName); // имя объекта Mutex

В качестве атрибутов защиты можно указать значение NULL.

Параметр bInitialOwner определяет начальное состояние объекта Mutex. Если он имеет значение TRUE, задача, создающая объект Mutex, будет им владеть сразу после создания. Если же значение этого параметра равно FALSE, после создания объект Mutex не будет принадлежать ни одной задаче.

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

Если объект Mutex используется задачами одного процесса, вместо адре­са имени можно указать значение NULL, в этом случае будет создан "безымянный" объект Mutex.

Функция CreateMutex возвращает идентификатор созданного объекта Mutex или NULL при ошибке.

Зная имя объекта Mutex, задача может открыть его с помощью функции OpenMutex. С помощью этой функции несколько задач могут открыть один и тот же объект Mutex и затем выполнять ожидание для этого объекта:

HANDLE OpenMutex(

DWORD fdwAccess, // требуемый доступ

BOOL flnherit, // флаг наследования

LPCTSTR IpszMutexName); // адрес имени объекта Mutex.

Назначение параметров аналогично описанным выше при рассмотрении функции OpenEvent.

Зная идентификатор объекта Mutex, полученный от функции CreateMutex или OpenMutex, задача может завладеть объектом при помощи функций ожидания

событий WaitForSingleObject, WaitForMultipleObject, описанных выше.

Для отказа от владения объектом Mutex, т.е. для его освобождения, вы

должны воспользоваться функцией ReleaseMutex:

BOOLReleaseMutex(HANDLE hMutex);

В качестве единственного параметра функции необходимо передать идентификатор объекта Mutex, полученный от функции CreateMutex или OpenMutex.

Если объект Mutex больше не нужен, вы должны освободить его иден­тификатор при помощи универсальной функции CloseHandle.

Блокирующие функции

Если несколько задач выполняют изменение одной и той же глобальной переменной, их необходимо синхронизировать, чтобы одновременно к этой переменной обращалась только одна задача. В программном интерфейсе Mi­crosoft Windows предусмотрено несколько функций, которые могут оказаться полезными в данной ситуации.

Функции InterlockedIncrement и InterlockedDecrement выполняют соот­ветственно увеличение и уменьшение на l значения переменной типа LONG, адрес которой передается им в качестве единственного параметра.

LONG InterlockedIncrement(LPLONG lpAddent);

LONG InterlockedDecrement(LPLONG lpAddent);

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

Для записи в некоторую глобальную переменную нового значения ис­пользуется функция InterlockedExchange:

LONG InterlockedExchange(

LPLONG lpTarget, // адрес изменяемой переменной

LONG INewValue); //новое значение для переменной

Эта функция записывает значение lNewValue по адресу lpTarget. При ом гарантируется, что операция не будет прервана другой задачей, выпол­няющейся в рамках того же процесса.

Все рассмотренные функции возвращают старое значение изменяемой переменной.

Наши рекомендации