Семафоры
Объекты ядра «семафор» используются для учета ресурсов Как и все объекты ядра, они содержат счетчик числа пользователей, но, кроме того, поддерживают два 32 битных значения со знаком: одно определяет максимальное число ресурсов (контро лируемое семафором), другое используется как счетчик текущего числа ресурсов
Попробуем разобраться, зачем нужны все эти счетчики, и для примера рассмот рим программу, которая могла бы использовать семафоры. Допустим, я разрабатываю серверный процесс, в адресном пространстве которого выделяется буфер для хране ния клиентских запросов. Размер этого буфера «зашит» в код программы и рассчи тан на хранение максимум пяти клиентских запросов. Если новый клиент пытается связаться с сервером, когда эти пять запросов еще не обработаны, генерируется ошиб ка, которая сообщает клиенту, что сервер занят и нужно повторить попытку позже При инициализации мой серверный процесс создает пул из пяти потоков, каждый из которых готов обрабатывать клиентские запросы по мере их поступления.
Изначально, когда запросов от клиентов еще нет, сервер не разрешает выделять процессорное время каким-либо потокам в пуле. Но как только серверу поступает, скажем, три клиентских запроса одновременно, три потока в пуле становятся плани руемыми, и система начинает выделять им процессорное время Для слежения за ре сурсами и планированием потоков семафор очень удобен. Максимальное число ре сурсов задается равным 5, что соответствует размеру буфера. Счетчик текущего чис ла ресурсов первоначально получает нулевое значение, так как клиенты еще не выда ли ни одного запроса. Этот счетчик увеличивается на 1 в момент приема очередного клиентского запроса и на столько же уменьшается, когда запрос передается на обра ботку одному из серверных потоков в пуле.
Для семафоров определены следующие правила:
Не путайте счетчик текущего числа ресурсов со счетчиком числа пользователей объекта-семафора
Объект ядра «семафор» создается вызовом CreateSemapbore
HANDLE CreateSemaphore( PSECURITY_ATTRIBUTE psa, LONG lInitialCount, LONG lMaximumCount, PCTRTR pszName)
О параметрах psa и pszName я рассказывал в главе 3 Разумеется, любой процесс может получить свой («процессо-зависимый») описатель существующего объекта «се мафор», вызвав OpenSemaphore
HANDLE OpenSemaphore( DWORD fdwAccess, BOOL bInhentHandle, PCTSTR pszName);
Параметр lMaximumCount сообщает системе максимальное число ресурсов, обра батываемое Вашим приложением Поскольку это 32-битное значение со знаком, пре дельное число ресурсов можетдостигать 2 147 483 647 Параметр lInitiа1Соипt указы вает, сколько из этих ресурсов доступно изначально (на данный момент) При ини циализяции моего серверного процесса клиентских запросов нет, поэтому я вызы ваю CreateSemaphore так
HANDLE hSem = CreateSemaphore(NULL, 0, 5, NULL);
Это приводит к созданию семафора со счетчиком максимального числа ресурсов равным 5, при этом изначально ни один ресурс не доступен (Кстати, счетчик числа пользователей данного объекта ядра равен 1, так как я только что создал этот объект, не запутайтесь в счетчиках) Поскольку счетчику текущего числа ресурсов присвоен 0 семафор находится в занятом состоянии А это значит, что любой поток, ждущий се мафор, просто засыпает
Поток получаст доступ к ресурсу, вызывая одну из Wait-функций и передавая ей описатель семафора, который охраняет этот ресурс Wait-функция проверяет у сема фора счетчик гекущего числа ресурсов если его значение больше 0 (семафор свобо ден), уменьшает значение этого счетчика на 1, и вызывающий поток остается плани руемым Очень важно, что семафоры выполняют эту операцию проверки и присвое ния на уровне атомдрного доступа; иначе говоря, когда Вы запрашиваете у семафора какой-либо ресурс, операционная система проверяет, доступен ли этот ресурс, и, если да, уменьшает счетчик текущего числа ресурсов, не позволяя вмешиваться в эту опе рацию другому потоку Только после того как счетчик ресурсов будет уменьшен на 1, доступ к ресурсу сможет запросить другой поток
Если Wait- функция определяет, что счетчик текущего числа ресурсов равен 0 (се мафор занят), система переводит вызывающий поток в состояние ожидания Когда другой поток увеличит значение этого счетчика, система вспомнит о ждущем потоке и снова начнет выделять ему процессорное время (а он, захватив ресурс, уменьшит значение счетчика на 1).
Поток увеличивает значение счетчика текущего числа ресурсов, вызывая функцию ReleaseSemaphore
BOOL ReleaseSemaphore( HANDLE hSem,
LONG lReleaseCount, PLONG p]PreviousCount);
Она просто складывает величину lReleaseCount со значением счетчика текущего числа ресурсов. Обычно в параметре lReleaseCount передают 1, но это вовсе не обяза тельно: я часто передаю в нем значения, равные или большие 2. Функция возвращает исходное значение счетчика ресурсов в *plPreviousCount Если Вас не интересует это значение (а в большинстве программ так оно и есть), передайте в параметре plPre viousCount значение NULL.
Было бы удобнее определять состояние счетчика текущего числа ресурсов, не меняя его значение, но такой функции в Windows нет. Поначалу я думал, что вызовом ReleaseSemapbore с передачей ей во втором параметре нуля можно узнать истинное значение счетчика в переменной типа LONG, на которую указывает параметр plPre viousCount. Но не вышло: функция занесла туда пуль. Я передал во втором параметре заведомо большее число, и — тот же результат. Тогда мне стало ясно: получить значе ние этого счетчика, не изменив его, невозможно.