Сравнительный обзор: мьютексы и объекты CRITICAL_SECTION
Как уже неоднократно упоминалось, мьютексы и объекты CRITICAL_SECTION весьма напоминают друг друга и предназначены для решения одного и того же круга задач. В частности, объекты обоих типов могут принадлежать только одного потока, и если объектом, которым уже владеет какой-либо поток, пытаются завладеть другие потоки, то они будут блокированы до тех пор, пока объект не освободится. Мьютексы могут обеспечивать большую гибкость, однако достигается это лишь за счет ухудшения производительности. В заключение перечислим наиболее важные отличия, существующие между указанными двумя типами объектов синхронизации.
• Мьютексы, покинутые завершающимися потоками, переходят в сигнальное состояние, в результате чего другие потоки не будут блокироваться на неопределенное время.
• Имеется возможность организовать ожидание мьютекса с использованием конечного интервала ожидания, тогда как в случае объектов CS возможен только опрос их состояния.
• Мьютексам можно присваивать имена, и их могут совместно использовать потоки, принадлежащие разным процессам.
• К мьютексам применима функция WaitForMultipleObjects, что не только удобно с точки зрения программирования, но и позволяет избежать взаимоблокировки потоков при надлежащей организации программы.
• Поток, создающий мьютекс, может сразу же указать, что он становится его владельцем. В случае объектов CS за право владения объектом могут состязаться несколько потоков.
• Обычно, хотя и не всегда, использование объектов CS обеспечивает более высокую производительность по сравнению с той, которая достигается при использовании мьютексов. Этот вопрос более подробно обсуждается в главе 9.
В NT для синхронизации доступа к кучам (глава 5) предусмотрены две функции — HeapLock и HeapUnlock. В каждой из этих функций единственным аргументом является дескриптор. Эти функции удобно применять в тех случаях, когда используется флаг HEAP_NO_SERIALIZE, или когда потоку необходимы права исключительного доступа к куче.
Объекты второго из трех упомянутых в начале главы типов объектов синхронизации ядра — семафоры (semaphores), поддерживают счетчики, и когда значение этого счетчика больше 0, объект семафора находится в сигнальном состоянии. Если же значение счетчика становится нулевым, объект семафора переходит в несигнальное состояние.
Потоки и процессы организуют ожидание обычным способом, используя для этого одну или несколько функций ожидания. При разблокировании ожидающего потока значение счетчика уменьшается на 1.
К функциям управления семафорами относятся CreateSemaphore, OpenSemaphore и ReleaseSemaphore, причем последняя функция может инкрементировать значение счетчика на 1 и более. Эти функции аналогичны своим эквивалентам, предназначенным для управления мьютексами.
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpsa, LONG lSemInitial, LONG lSemMax, LPCTSTR lpSemName)
Параметр 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-сайте книги.
Читать дальше