При работе нескольких независимых процессов использующих общие ресурсы, возникает задача синхронизации доступа. Ресурсом может выступать как файл или строчка в файле, так и доступ к железу или же ожидание завершения вычисления в параллельном процессе. Для реализации синхронизации между процессами необходимо иметь возможность межпотокового обмена данными.
Межпотоковый обмен (inter-thread communication) — термин, который означает обмен данными между потоками одного или разных процессов в программировании. Это позволяет потокам координировать действия, решать общую проблему или совместно использовать ресурсы.
В OORexx для реализации многопоточной работы есть инструкции «guard» и «reply». Но они относятся к работе в рамках одного процесса. Для синхронизации работы разных процессов можно использовать семафоры и внешние очереди (каналы). Вот о них мы и поговорим.
Семафоры - примитив синхронизации работы процессов и потоков, в основе которого лежит счётчик, над которым можно производить две атомарные операции: увеличение и уменьшение значения на единицу. Семафоры являются инструментом операционной системы, и работа с ними реализована почти во всех языках программирования.
В ряде языках программирования присутствует понятие «внешняя очередь» (канал) но, как правило, они не совместимы между языками. В OORexx есть своя реализация внешних очередей (каналов) доступ к которой программы, написанные на других языках программирования, могут получить через специальный механизм, но его обсуждение выходит за рамки текущего материала.
Для реализации задачи блокировки ресурсов был, разработал класс «LockObject». Он обеспечивает блокировку ресурса, как через семафор, так и через внешние очереди (каналы). Главным условием для блокировки ресурса и определения её наличия является единообразное формирование "имени ресурса" (ObjectName) во всех процессах, работающих с ним. Например, для блокировки файла, можно указать полный путь и имя файла. В "ObjectName" можно помещать строки любого содержания, все необходимые доработки происходят внутри методов.
При блокировке через семафор определить какой процесс поставил блокировку нельзя и может возникнуть ситуация когда процесс блокирует сам себя. По этому, этот способ больше подходит для блокировки на короткий период времени, например однократной записи в файл.
При блокировке через внешние очереди (каналы) можно определить, какой процесс поставил блокировку и избежать ситуации, когда процесс блокирует сам себя. Что подходит для логической блокировки чего либо, например, для многократной записи в файл на длительном отрезке времени. Для определения процесса установившего блокировку, используется глобальная переменная "local~!ProgramCode", она должна быть объявлена в основной программе.
При некорректном закрытии процесса (например, по кресту у окна) метод "UNINIT" не вызывается, соответственно блокировка не снимается. При блокировке через внешнюю очередь (канал), несмотря на то, что блокировка остаётся, процесс с тем же ".!ProgramCode" получит доступ к заблокированному ресурсу. При блокировке через семафор ситуация иная, её описал Dom Wise:
В Windows нет понятия процесса‑владельца для именованных объектов, таких как общие мьютексы. При создании общего объекта ему присваивается <количество дескрипторов>, равное 1. При каждом последующем открытии (которое включает дополнительные вызовы SysCreateMutexSem в Windows) количество дескрипторов увеличивается на 1, а при каждом закрытии уменьшается на 1. При завершении процесса Windows автоматически закрывает все дескрипторы, которые процесс ещё не закрыл, но не освобождает мьютексы, которыми всё ещё владеет.
Так как в классе обеспечивает ситуацию, когда у каждого семафора только один хозяин, то при некорректном завершении процесса семафор остаётся "бесхозным" и зачищается операционной системой. Что позволяет провести блокировку ресурса в других процессах. Подробнее про ошибки при создание семафоров (SysRequestMutexSem) можно посмотреть здесь.
В классе «LockObject» реализованы следующие публичные методы:
New |
Создание объекта и установка блокировки ресурса описанного в "ObjectName". |
UnInit |
Снятие блокировки ресурса перед уничтожением экземпляра класса, в том числе при аварийном закрытии программы. |
Lock |
Блокируем ресурс, описанный в "ObjectName". |
UnLock |
Снимаем блокировку с ресурса, описанного в "ObjectName". |
Примеры создания экземпляра класса и обращения к нему:
sem=.LockObject~new(ObjectName, [WaitingTime=''], [Type='queue'])
sem~UnLock
sem=.LockObject~new('ИмяЧегоЛибо', WaitingTime)
sem=.LockObject~new('ИмяЧегоЛибо', WaitingTime, 'queue')
sem=.LockObject~new('c:\test\test.txt', WaitingTime, 'sem')
где:
ObjectName |
Имя ресурса для блокировки. Строка на основе, которой будет строиться имя семафорам или канала (LockName). |
WaitingTime |
Время ожидания, в микросекундах. Время, которое система будет ждать завершения критических операций (например, доступ к файлу), после его истечения возвращается ошибка. [''] WaitingTime='', ожидание без ограничения. WaitingTime=0, не ждать. |
Type |
Тип блокировки семафор (Sem)/публичная очередь (Queue) [Queue] |
Публичные атрибуты класса:
Type |
Тип блокировки семафор (Sem)/публичная очередь (Queue) [Queue] |
ObjectName |
Имя ресурса для блокировки. Нормализованная строка на основе, которой строиться имя семафорам или канала (LockName). |
LockName |
Техническое имя семафора или канала. |
WaitingTime |
Время ожидания, в микросекундах. Время, которое система ждёт завершения критических операций (например, доступ к файлу), после его истечения возвращается ошибка. [''] WaitingTime='', ожидание без ограничения. WaitingTime=0, не ждать. |
ErrCode |
Код последней ошибки/возврата. 00 - Нет ошибки. 01 - Некорректные данные на входе. 02 - Попытка закрыть не существующую блокировку. 03 - Ошибка при создании семафора: 10 - Время ожидания истекло до завершения операции. Выполнение команды было прервано. |
ErrText |
Текст с описанием последней ошибки/сообщения. |
Пример применения в коде:
sem=.LockObject~new('c:\test\test.txt', WaitingTime)
if sem~ErrCode<>0 then do
say 'Ошибка в class LockObject. 'sem~ErrText; return 1 -- <!> ERROR! АВАРИЙНЫЙ выход!!!
end -- if
...
sem~UnLock
Файл с кодом класса «LockObject» можно скачать по этой ссылки (резервная копия).