Механизм мьютексов предназначен для того чтобы обеспечить работу с ресурсом не более одного потока одновременно.
В Delphi существует два способа работы с мьютексами.
- Использование «обычных» для этих целей низкоуровневых (WinAPI) функций (CreateMutex и т.д.).
Данный способ работает практически во всех версиях, давно освоен программистами и не нуждается в дополнительном описании (материалов на эту тему по Delphi в интернете более чем достаточно). Назовём его «классическим способом»; - Использование класса TMutex.
Этот класс впервые появился в версии XE Он предоставляет простой интерфейс для работы с мьютексами и почти полностью избавляет от необходимости использования низкоуровневых функций при работе с ними.
Рассмотрим второй способ.
Класс TMutex описан в модуле System.SyncObjs. Его конструктор имеет три перегрузки.
Наиболее предпочтительна для создания мьютекса следующая перегрузка:
1 2 3 4 |
var mutex: TMutex; ... mutex := TMutex.Create(nil, true, 'NAME'); |
- Первый параметр (TSecurityAttributes) — указатель на запись атрибутов защиты (в простейшем случае допускается nil).
- Второй параметр (Boolean) – определяет, станет ли поток, создавший мьютекс его владельцем;
- Третий параметр (String) — уникальное имя мьютекса.
- Четвёртый параметр (Boolean) – способность мьютекса работать с компонентами STA (single-threaded apartment) COM. Это параметр необязательный и по умолчанию имеет значение false.
Вторая перегрузка открывает уже существующий мьютекс по уникальному имени (принимаемые параметры аналогичны предыдущей).
Третья перегрузка создаёт новый мьютекс, но принимает только один параметр, возможность работы с STA COM.
В процессе работы с TMutex в основном используются три его метода:
- Acquire – захват мьютекса;
- Release – освобождение мьютекса;
- WaitFor – ожидание доступа к мьютексу в течение некоторого времени (таймаута). Метод унаследован от предка – класса THandleObject.
Продолжительность таймаута задаётся единственным параметром этого метода. Если требуется ожидать неопределённый период времени, передаётся значение INFINITE.
В отличие от используемой в «классическом способе» функции WaitForSingleObject, данный метод возвращает значение перечисления TWaitResult. Поэтому, сравнить его с WAIT_TIMEOUT напрямую не получится (в прочем это и не нужно).
В предыдущей статье посвящённой мьютексам уже было отмечено, что существуют два основных подхода к их применению.
- В зависимости от состояния мьютекса соответствующие действия выполняются сразу. Поток, обратившийся к мьютексу, не приостанавливает своей работы;
- Поток, обратившийся к мьютексу, ожидает его освобождения и только после этого продолжает работу.
Покажем реализацию обоих подходов с использованием TMutex.
Первый подход реализуется следующим образом. Выполняется проверка занятости мьютекса с помощью функции GetLastError и в зависимости от её результатов производятся те или иные действия. Однако добавляются вызовы методов захвата и освобождения мьютекса, что делает код несколько более громоздким.
1 2 3 4 5 6 7 8 |
if GetLastError <> ERROR_ALREADY_EXISTS then begin mutex.Acquire; //Действия если мьютекс не занят mutex.Release; end else //Действия если мьютекс занят |
Мьютекс, занятый с помощью метода Acquire, необходимо впоследствии обязательно освободить с помощью метода Release. Иначе мьютекс так и останется занятым и другой поток не сможет получить к нему доступ.
Реализация второго подхода почти полностью повторяет «классический способ». Только вместо двух параметров (дескриптор и время) используется всего один.
Важно отметить, что в примере выше дескриптор также не используется. Всю «низкоуровневую» работу уже сделали программисты Embarcadero.
1 2 3 4 |
if mutex.WaitFor(5000) = TWaitResult.wrTimeout then //Действия, если за время ожидания мьютекс не освободился else //Действия, если за время ожидания мьютекс освободился |
По завершении работы с мьютексом, когда в нём уже нет необходимости, он уничтожается стандартным способом.
1 |
mutex.Free; |
В отношении работы с TMutex можно отметить следующее.
Достоинства:
- Простота и удобство работы.
Нет необходимости манипулировать дескрипторами и низкоуровневыми функциями. Практически весь необходимый функционал собран в одном классе; - Более широкие возможности и большая гибкость прикладных программ.
Так как TMutex это, по сути, «родной» класс Delphi. С его экземпляром можно работать как с обычным объектом.
Недостатки:
- Некоторые операции требуют дополнительного вызова методов Acquire и Release.
Может доставить неудобства скорее из-за непривычности; - TMutex недоступен в версиях Delphi ниже XE.
В принципе, все перечисленные недостатки можно смело назвать несущественными.
Поэтому не случайно Embarcadero, начиная с версии XE2, настоятельно рекомендует выполнять все операции с мьютексами при помощи этого класса.
Возможность использования «классического способа» по-прежнему остается, так как он основан на WinAPI. Но, при решении прикладных задач он существенно проигрывает TMutex. Поэтому, если используется современная версия Delphi и при этом речь не идёт о разработке системных утилит или решении других подобных задач использование TMutex более предпочтительно.
Добавить комментарий