Мьютексы
(Mutexes)
Критические
секции просты в использовании и обладают высоким быстродействием, но не обладают
гибкостью в управлении. Нет, например, возможности установить время блокирования
или присвоить имя критической секции для того, чтобы два разных процесса могли
иметь с ней дело. Оба эти недостатка можно устранить, если использовать такой
объект ядра, как mutex. Термин mutex происходит от mutually exclusive
(взаимно исключающий). Этот объект обеспечивает исключительный (exclusive)
доступ к охраняемому блоку кодов. Например, если несколько процессов должны
одновременно работать с одним и тем же связным списком, то на время выполнения
каждой операции: добавления, удаления элемента или сортировки, следует заблокировать
список и разрешить доступ к нему только одному из процессов.
Для синхронизации
потоков разных процессов следует объявить один общедоступный объект класса CMutex,
который будет управлять доступом к списку. Мыо-текс предоставляет доступ к объекту
любому из потоков, если в данный момент объект не занят, и запоминает текущее
состояние объекта. Если объект занят, то мьютекс запрещает доступ. Однако можно
подождать освобождения объекта с помощью функции WaitForSingleObject, в которой
роль управляющего объекта выполняет тот же мьютекс. Типичная тактика использования
такова. Объект
CMutex
mutex;
необходимо
объявить заранее. Обычно он является членом thread-safe-класса. В точке,
где необходимо защитить код, создается объект класса CSingleLock, которому передается
ссылка на мьютекс. При попытке включения блокировки вызовом метода Lock надо
в качестве параметра указать время (в миллисекундах), в течение которого следует
ждать освобождения объекта, охраняемого мьютексом. В течение этого времени либо
получим доступ к объекту, либо не получим его. Если объект стал доступен, то
мы запираем его от других потоков и производим работу, которая требует синхронизации.
После этого освобождаем блокировку. Если время ожидания истекло и доступ к объекту
не получен, то обработка этой ситуации (ветвь else) целиком в нашей власти.
Если задать ноль в качестве параметра функции Lock, то ожидания не будет. Напротив,
можно ждать неопределенно долго, если передать константу INFINITE.
Другой процесс,
если он знает, что существует мьютекс с каким-то именем, может сделать этот
объект доступным для себя, открыв уже существующий мьютекс. При вызове функции
OpenMutex система сканирует существующие объекты-мьютексы, проверяя, нет ли
среди них объекта с указанным именем. Обнаружив таковой, она создает описатель
объекта, специфичный для данного процесса. Теперь любой поток данного процесса
может использовать описатель в целях синхронизации доступа к какому-то коду
или объекту. Когда мьютекс становится ненужным, следует освободить его вызовом
CloseHandle(HANDLE
hObject);
где hObject
— описатель мьютекса. Когда система создает мьютекс, она присваивает ему имя
(строка в стиле С). Это имя используется при совместном доступе к мыотексу нескольких
процессов. Если несколько потоков создают объект с одним и тем же именем, то
только первый вызов приводит к созданию мьютекса. Имя используется при совместном
доступе нескольких процессов. Если оно совпадает с именем уже существующего
объекта, конструктор создает новый экземпляр класса CMutex, который ссылается
на существующий мьютекс с данным именем. Если имя не задано (IpszName равен
NULL) мьютекс будет неименованным, и им можно пользоваться только в пределах
одного процесса.
С любым объектом
ядра сопоставляется счетчик, фиксирующий, сколько раз данный объект передавался
во владение потокам. Если поток вызовет, например, CSingleLock: :Lock() ИЛИ
WaitForSingleObject () ДЛЯ уже принадлежащего ему объекта, он сразу же получит
доступ к защищаемым этим объектом данным, так как система определит, что поток
уже владеет этим объектом. При этом счетчик числа пользователей объекта увеличится
на 1. Теперь, чтобы перевести объект в свободное состояние, потоку необходимо
соответствующее число раз вызвать CSingleLock::Unlock() ffilHReleaseMutex()
. Функции EnterCriticalSection и LeaveCriticalSection действуют по отношению
к критическим секциям аналогичным образом.
Объект-мьютекс
отличается от других синхронизирующих объектов ядра тем, что занявшему его потоку
передаются права на владение им. Прочие синхронизирующие объекты могут быть
либо свободны, либо заняты и только, а мьютексы способны еще и запоминать, какому
потоку они принадлежат. Отказ от мьютекса происходит, когда ожидавший его поток
захватывает этот объект, переводя его в занятое состояние, а потом завершается.
В таком случае получается, что мьютекс занят и никогда не освободится, поскольку
другой поток не сможет этого сделать. Система не допускает подобных ситуаций
и, заметив, что произошло, автоматически переводит мьютекс в свободное состояние.