Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр.

[Назад] [Содеожание] [Дальше]

Синхронизация с использованием семафоров

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

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

Ресурс, доступ к которому управляется семафором, может быть не только физическим устройством, но и чисто логическим.

Поясним это на примере.

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

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

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

Если теперь в нашем новом варианте приложения пользователь закроет MDI-окно, в котором выполняется рисование, то одно из двух “спящих” MDI-окон должно “проснутся”. При этом в нем начнется процесс рисования эллипсов. Если закрыть еще одно активное окно, процесс рисования должен начаться во втором “спящем” окне.

Таким образом, сколько бы MDI-окон не создал пользователь, эллипсы будут отображаться только в трех из них, так как только три задачи будут работать, а остальные будут находиться в состоянии ожидания.

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

Как работает семафор

В отличие от железнодорожного семафора, который может быть либо открыт, разрашая движение, либо закрыт, запрещая его, семаформы в операционной системе Microsoft Windows NT действуют более сложным образом.

Так же как и объект Mutex, семафор может находиться в отмеченном или неотмеченном состоянии. Приложение выполняет ожидание для семафора при помощи таких функций, как WaitForSingleObject XE "WaitForSingleObject" или WaitForMultipleObject (точно также, как и для объекта Mutex). Если семафор находится в неотмеченном состоянии, задача, вызвавшая для него функцию WaitForSingleObject, находится в состоянии ожидания. Когда же состояние семафора становится отмеченным, работа задачи возобновляется. В такой логике работы для вас, однако, нет ничего нового.

В отличие от объекта Mutex, с каждым семафором связывается счетчик, начальное и максимальные значения которого задаются при создании семафора. Значение этого счетчика уменьшается, когда задача вызывает для семафора функцию WaitForSingleObject XE "WaitForSingleObject" или WaitForMultipleObject, и увеличивается при вызове другой, специально предназначенной для этого функции.

Если значение счетчика семафора равно нулю, он находится в неотмеченном состоянии. Если же это значение больше нуля, семафор переходит в отмеченное состояние.

Пусть, например, приложение создало семафор, указав для него максимальное значение счетчика, равное трем. Пусть начальное значение этого счетчика также будет равно трем.

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

Задача, запущенная четвертой, вызовет функцию WaitForSingleObject для неотмеченного семафора, в результате чего она будет ждать. Точно также, задачи, запущенные после запуска четвертой задачи, будут выполнять ожидание для того же семафора.

Как долго продлится ожидание?

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

На рис. 4.4 мы показали последовательное изменение счетчика семафора (обозначенного символом N) при запуске первых трех задач. Так как счетчик больше нуля, семафор открыт, то есть находится в отмеченном состоянии и поэтому функция WaitForSingleObject XE "WaitForSingleObject" не будет выполнять ожидание.

Рис. 4.4. Изменение значения счетчика семафора от трех до единицы

После запуска четвертой задачи, выполняющей ожидание семафора, значение счетчика уменьшится до нуля. При этом семафор будет закрыт, то есть перейдет в неотмеченное состояние (рис. 4.5).

Рис. 4.5. После запуска четвертой задачи семафор закрывается

Функции для работы с семафорами

Рассмотрим функции программного интерфейса операционной системы Microsoft Windows NT, предназначенные для работы с семафорами.

Создание семафора

Для создания семафора приложение должно вызвать функцию CreateSemaphore XE "CreateSemaphore" , прототип которой приведен ниже:


HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // атрибуты
                                               // защиты 
  LONG lInitialCount,  // начальное значение счетчика семафора 
  LONG lMaximumCount,  // максимальное значение 
                       // счетчика семафора 
  LPCTSTR  lpName);     // адрес строки с именем семафора 

Этой функцией пользоваться достаточно просто.

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

Через параметры lInitialCount и lMaximumCount передается, соответственно, начальное и максимальное значение счетчика, связанного с создаваемым семафором. Начальное значение счетчика lInitialCount должно быть больше или равно нулю и не должно превосходить максимальное значение счетчика, передаваемое через параметр lMaximumCount.

Имя семафора указывается аналогично имени рассмотренного нами ранее объекта Mutex с помощью параметра lpName.

В случае удачного создания семафора функция CreateSemaphore возвращает его идентификатор. В случае возникновения ошибки возвращается значение NULL, при этом код ошибки можно узнать при помощи функции GetLastError XE "GetLastError" .

Так как имена семафоров доступны всем приложениям в системе, возможно возникновение ситуации, когда приложение пытается создать семафор с уже использованным именем. При этом новый семафор не создается, а приложение получает идентификатор для уже существующего семафора. Если возниклав такая ситуация, функция GetLastError XE "GetLastError" , вызванная сразу после функции CreateSemaphore XE "CreateSemaphore" , возвращает значение ERROR_ALREADY_EXISTS XE "ERROR_ALREADY_EXISTS" .

Уничтожение семафора

Для уничтожения семафора вы должны передать его идентификатор функции CloseHandle. Заметим, что при завершении процесса все созданные им семафоры уничтожаются автоматически.

Открывание семафора

Если семафор используется только для синхронизации задач, созданных в рамках одного приложения, вы можете создать безымянный семафор, указав в качестве параметра lpName функции CreateSemaphore XE "CreateSemaphore" значение NULL. В том случае, когда необходимо синхронизовать задачи разных процессов, следует определить имя семафора. При этом один процесс создает семафор с помощью функции CreateSemaphore, а второй открывает его, получая идентификатор для уже существующего семафора.

Существующий семафор можно открыть функцией OpenSemaphore XE "OpenSemaphore" , прототип которой приведен ниже:


HANDLE OpenSemaphore(
  DWORD   fdwAccess,           // требуемый доступ 
  BOOL    fInherit,            // флаг наследования 
  LPCTSTR lpszSemaphoreName ); // адрес имени семафора 

Флаги доступа, передаваемые через параметр fdwAccess, определяют требуемый уровень доступа к семафору. Этот параметр может быть комбинацией следующих значений:

 Значение

 Описание

 SEMAPHORE_ALL_ACCESS

 Указаны все возможные флаги доступа

 SEMAPHORE_MODIFY_STATE

 Возможно изменение значение счетчика семафора функцией ReleaseSemaphore XE "ReleaseSemaphore"

 SYNCHRONIZE

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

Параметр fInherit определяет возможность наследования полученного идентфикатора. Если этот параметр равен TRUE, идентфикатор может наследоваться дочерними процессами. Если же он равен FALSE, наследование не допускается.

Через параметр lpszSemaphoreName вы должны передать функции адрес символьной строки, содержащей имя семафора.

Если семафор открыт успешно, функция OpenSemaphore возвращает его идентификатор. При ошибке возвращается значение NULL. Код ошибки вы можете определить при помощи фукнции GetLastError XE "GetLastError" .

Увеличение значения счетчика семафора

Для увеличения значения счетчика семафора приложение должно использовать функцию ReleaseSemaphore:


BOOL ReleaseSemaphore(
  HANDLE hSemaphore,        // идентификатор семафора 
  LONG   cReleaseCount,     // значение инкремента 
  LPLONG lplPreviousCount); // адрес переменной для записи 
                   // предыдущего значения счетчика семафора

Функция ReleaseSemaphore увеличивает значение счетчика семафора, идентификатор которого передается ей через параметр hSemaphore, на значение, указанное в параметре cReleaseCount.

Заметим, что через параметр cReleaseCount вы можете передавать только положительное значение, большее нуля. При этом если в результате увеличения новое значение счетчика должно будет превысить максимальное значение, заданное при создании семафора, функция ReleaseSemaphore XE "ReleaseSemaphore" возвращает признак ошибки и не изменяет значение счетчика.

Предыдущее значение счетчика, которое было до использования функции ReleaseSemaphore, записывается в переменную типа LONG. Адрес этой переменной передается функции через параметр lplPreviousCount.

Если функция ReleaseSemaphore завершилась успешно, она возвращает значение TRUE. При ошибке возвращается значение FALSE. Код ошибки в этом случае можно определить, как обычно, при помощи функции GetLastError XE "GetLastError" .

Функция используется обычно для решения двух задач.

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

Во-вторых, эта функция может быть использована на этапе инициализации мультизадачного приложения. Создавая семафор с начальным значением счетчика, равным нулю, главная задача блокирует работу задач, выполняющих ожидание этого семафора. После завершения инициализации главная задача с помощью функции ReleaseSemaphore XE "ReleaseSemaphore" может увлеичить значение счетчика семафора до максимального, в результате чего известное количество ожидающих задач будет активизировано.

Уменьшение значения счетчика семафора

В программном интерфейсе операционной системы Microsoft Windows NT нет фукнции, специально предназначенной для уменьшения значения счетчика семафора. Этот счетчик уменьшается, когда задача вызывает функции ожидания, такие как WaitForSingleObject XE "WaitForSingleObject" или WaitForMultipleObject. Если задача вызывает несколько раз функцию ожидания для одного и того же семафора, содержимое его счетчика каждый раз будет уменьшаться.

Определение текущего значения счетчика семафора

Единаственная возможность определения текущего значения счетчика семафора заключается в увеличении этого значения функцией ReleaseSemaphore XE "ReleaseSemaphore" . Значение счетчика, которое было до увеличения, будет записано в переменную, адрес которой передается функции ReleaseSemaphore через параметр lplPreviousCount.

Заметим, что в операционной системе Microsoft Windows NT не предусмотрено средств, с помощью которых можно было бы определить текущее значение семафора, не изменяя его. В частности, вы не можете задать функции ReleaseSemaphore XE "ReleaseSemaphore" нулевое значение инкремента, так как в этом случае указанная функция просто вернет соответствующий код ошибки.

[Назад] [Содеожание] [Дальше]