Последовательный доступ к ресурсам
В программном интерфейсе ОС 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.
Блокирующие функции
Если несколько задач выполняют изменение одной и той же глобальной переменной, их необходимо синхронизировать, чтобы одновременно к этой переменной обращалась только одна задача. В программном интерфейсе Microsoft Windows предусмотрено несколько функций, которые могут оказаться полезными в данной ситуации.
Функции InterlockedIncrement и InterlockedDecrement выполняют соответственно увеличение и уменьшение на l значения переменной типа LONG, адрес которой передается им в качестве единственного параметра.
LONG InterlockedIncrement(LPLONG lpAddent);
LONG InterlockedDecrement(LPLONG lpAddent);
Особенностью этих функций является то, что если одна задача приступила с их помощью к изменению значения переменной, то другая не сможет выполнить изменение этой переменной до тех пор, пока первая задача не завершит такое изменение.
Для записи в некоторую глобальную переменную нового значения используется функция InterlockedExchange:
LONG InterlockedExchange(
LPLONG lpTarget, // адрес изменяемой переменной
LONG INewValue); //новое значение для переменной
Эта функция записывает значение lNewValue по адресу lpTarget. При ом гарантируется, что операция не будет прервана другой задачей, выполняющейся в рамках того же процесса.
Все рассмотренные функции возвращают старое значение изменяемой переменной.