Параметр lSemMax, значение которого должно быть равным, по крайней мере, 1, определяет максимально допустимое значение счетчика семафора. Параметр lSemInitial — начальное значение этого счетчика, которое должно удовлетворять следующему условию: 0≤ lSemInitial≤ lSemMax и никогда не должно выходить за пределы указанного диапазона. Возвращение функцией значения NULL указывает на ее неудачное выполнение.
Каждая отдельная операция ожидания может уменьшить значение счетчика только на 1, но с помощью функции ReleaseSemaphore значение его счетчика может быть увеличено до любого значения вплоть до максимально допустимого.
BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG cReleaseCount, LPLONG lpPreviousCount)
Обратите внимание на возможность получения предыдущего значения счетчика, определяемого указателем lpPreviousCount, которое он имел до освобождения объекта синхронизации при помощи функции ReleaseSemaphore, но если необходимости в этом нет, то значение упомянутого указателя следует установить равным NULL.
Число, прибавляемое к счетчику семафора (cReleaseCount), должно быть больше 0, но если выполнение функции ReleaseSemaphore приводит к выходу значения счетчика за пределы допустимого диапазона, то она завершается с ошибкой, возвращая значение FALSE, а значение счетчика семафора остается неизменным. Предыдущим значением счетчика следует пользоваться с осторожностью, поскольку оно могло быть изменено другими потоками. Кроме того, невозможно определить, достиг ли счетчик максимально допустимого значения, поскольку не предусмотрено средство, отслеживающее увеличение счетчика в результате его освобождения. Пример использования предыдущего значения счетчика семафора приведен на Web-сайте книги.
Как ни соблазнительно пытаться рассматривать мьютекс как частный случай семафора, значение счетчика которого задано равным 1, это было бы заблуждением ввиду отсутствия понятия прав владения семафором. Семафор может быть освобожден любым потоком, а не только тем, который ожидает. Точно так же, поскольку нельзя говорить о правах владения семафором, отсутствует и понятие покинутого семафора.
Использование семафоров
Классической областью применения семафоров является управление распределением конечных ресурсов, когда значение счетчика семафора ассоциируется с определенным количеством доступных ресурсов, например, количеством сообщений, находящихся в очереди. Тогда максимальное значение счетчика соответствует максимальному размеру очереди. Таким образом, производитель помещает сообщение в буфер и вызывает функцию ReleaseSemaphore, обычно с увеличением значения счетчика на 1 (cReleaseCount). Потоки потребителя будут ожидать перехода семафора в сигнальное состояние, получая сообщения и уменьшая значения счетчика.
Вслед за рассмотрением программы 9.1 обсуждается другой важный случай применения семафоров, когда они используются для ограничения количества рабочих потоков, фактически выполняющихся в любой момент времени, что позволяет снизить состязательность между ними, а в некоторых случаях — повысить производительность. Эта методика, в которой используются дроссели семафоров (semaphore throttles), обсуждается в главе 9.
Опасность возникновения условий состязаний в программе sortMT (программа 7.2) иллюстрирует другое возможное применение семафоров, связанное с управлением точным количеством потоков, которые должны находиться в пробужденном состоянии. Можно создать все потоки, не приостанавливая их. После этого все они сразу же переходят к ожиданию перехода в сигнальное состояние семафора, инициализированного значением 0. Далее, главный поток вместо того, чтобы освобождать потоки, просто вызывает функцию ReleaseCount с увеличением счетчика, например, на 4 (или на любое другое значение, соответствующее количеству потоков), в результате чего возможность выполняться получат четыре потока.
Несмотря на все удобства, которые сулит использование семафоров, они являются в некотором смысле излишними в том смысле, что мьютексы и события (описанные в одном из следующих разделов), при условии их совместного использования, предлагают гораздо более широкие возможности, чем семафоры. Более подробная информация по этому поводу содержится в главе 10.
Ограниченность семафоров
В Windows существуют важные ограничения, касающиеся реализации семафоров. Например, каким образом поток может потребовать, чтобы счетчик семафора уменьшился на 2? Для этого поток мог бы организовать ожидание два раза подряд, как показано ниже, но эта операция не была бы атомарной, поскольку в промежутке между двумя вызовами функции ожидания данный поток может быть вытеснен. В результате этого, как описывается ниже, может наступить взаимоблокировка (deadlock) потоков.
/* hsem – дескриптор семафора. Максимальное значение счетчика семафора равно 2. */
…