Главная страница

Лабораторная 5 теория новая. Синхронизация процессов при работе с Windows


Скачать 55.53 Kb.
НазваниеСинхронизация процессов при работе с Windows
Дата09.05.2019
Размер55.53 Kb.
Формат файлаdocx
Имя файлаЛабораторная 5 теория новая.docx
ТипЗадача
#56727
страница1 из 2

Подборка по базе: Игротерапия, как средство развития познавательных процессов у де, отчет о проделанной работе волонтеров.docx, методические указания к сам. работе.docx, Информация по выпускной квалификационной работе.docx, Теория транспортных процессов и систем.doc, Отчет по лабораторной работе №2 Тема субд microsoft Access 2007, Теория случайных процессов.pdf, !Моделирование физических процессови явлений на ПК_Новосибирск.p, Синхронизация процессов и потоков.pdf, Охрана труда и правила личной гигиены при работе с собакой.docx.
  1   2


Синхронизация процессов при работе с Windows


Delphi , Компоненты и Классы , Потоки

Задача синхронизации встает при одновременном доступе нескольких процессов (или нескольких потоков одного процесса) к какому-либо ресурсу. Поскольку поток в Win32 может быть остановлен в любой, заранее ему неизвестный момент времени возможна ситуация, когда один из потоков не успел завершить модификацию ресурса (например, отображенной на файл области памяти), но был остановлен, а другой поток попытался обратиться к этому же ресурсу. В этот момент ресурс находится в несогласованном состоянии, и последствия обращения к нему могут быть самыми неожиданными – от порчи данных, до нарушения защиты памяти.

Главной идеей, положенной в основу синхронизации потоков в Win32 является использование объектов синхронизации и функций ожидания. Объекты могут находиться в одном из двух состояний – Signaled или Not Signaled. Функции ожидания блокируют выполнение потока до тех пор, пока заданный объект находится в состоянии Not Signaled. Таким образом, поток, которому необходим эксклюзивный доступ к ресурсу, должен выставить какой-либо объект синхронизации в несигнальное состояние, а по окончании – сбросить его в сигнальное. Остальные потоки должны перед доступом к этому ресурсу вызвать функцию ожидания, которая позволит им дождаться освобождения ресурса.

Рассмотрим, какие объекты и функции синхронизации предоставляет нам Win32 API.

Функции синхронизации

Функции синхронизации делятся на две основные категории – это функции, ожидающие единственного объекта и функции, ожидающие одного из нескольких объектов

Функции, ожидающие единственного объекта

Простейшей функцией ожидания является

function WaitForSingleObject(
hHandle: THandle;       // идентификатор объекта
dwMilliseconds: DWORD   // период ожидания
): DWORD; stdcall;

Функция ожидает перехода объекта hHandle в сигнальное состояние в течении dwMilliseconds миллисекунд. Если в качестве параметра dwMilliseconds передать значение INFINITE, функция будет ждать в течение неограниченного времени. Если dwMilliseconds равен 0, то функция проверяет состояние объекта и немедленно возвращает управление.

Функция возвращает одно из следующих значений:

WAIT_ABANDONED        Поток, владевший объектом, завершился, не переведя объект в сигнальное состояние.        

WAIT_OBJECT_0        Объект перешел в сигнальное состояние        

WAIT_TIMEOUT        Истек срок ожидания. Обычно в этом случае генерируется ошибка, либо функция вызывается в цикле до получения другого результата        

WAIT_FAILED        Произошла ошибка, например неверное значение hHandle. Более подробную информацию можно получить, вызвав GetLastError        

Следующий фрагмент кода запрещает Action1 до перехода объекта ObjectHandle в сигнальное состояние. Например, таким образом можно дожидаться завершения процесса, предав в качестве ObjectHandle его идентификатор, полученный функцией CreateProcess.

Code:

var
Reason: DWORD;
ErrorCode: DWORD;
 
Action1.Enabled := FALSE;
try
repeat
   Application.ProcessMessages;
   Reason := WailForSingleObject(ObjectHandle, 10);
   if Reason = WAIT_FAILED then begin
     ErrorCode := GetLastError;
     raise Exception.CreateFmt(
       'Wait for object failed with error: %d', [ErrorCode]);
   end;
until Reason <> WAIT_TIMEOUT;
finally
Actionl.Enabled := TRUE;
end;

В случае, когда требуется одновременно с ожиданием объекта, перевести в сигнальное состояние другой объект может использоваться функция:

function SignalObjectAndWait(
hObjectToSignal: THandle;  // объект, который будет переведен в
                            // сигнальное состояние 
hObjectToWaitOn: THandle;  // объект, которого ожидает функция
dwMilliseconds: DWORD;     // период ожидания
bAlertable: BOOL           // задает, должна ли функция возвращать
                            // управление в случае запроса на 
                            // завершение операции ввода-вывода
): DWORD; stdcall;

Возвращаемые значения аналогичны функции WaitForSingleObject.

!

В модуле Windows.pas эта функция ошибочно объявлена, как возвращающая значение BOOL. Если Вы намерены её использовать – объявите её корректно или используйте приведение типа возвращенного значения к DWORD

Объект hObjectToSignal может быть семафором, событием (event), либо мутексом. Параметр bAlertable определяет, будет ли прерываться ожидание объекта, в случае, если операционная система запросит у потока окончание операции асинхронного ввода-вывода, либо асинхронный вызов процедуры. Более подробно это обсуждается ниже.

Функции, ожидающие нескольких объектов

Иногда требуется задержать выполнение потока до срабатывания одного или всех сразу из группы объектов. Для решения подобной задачи служат следующие функции:

type
TWOHandleArray = array[0..MAXIMUM_WAIT_OBJECTS - 1] of THandle;
PWOHandleArray = ^TWOHandleArray;
 
function WaitForMultipleObjects(
nCount: DWORD;              // Задает количество объектов
lpHandles: PWOHandleArray;  // Адрес массива объектов
bWaitAll: BOOL;             // Задает, требуется ожидание всех
                             // объектов или любого
dwMilliseconds: DWORD       // Период ожидания
): DWORD; stdcall;

Функция возвращает одно из следующих значений:

Число в диапазоне от WAIT_OBJECT_0 до WAIT_OBJECT_0 + nCount – 1        Если bWaitAll равно TRUE, то это число означает, что все объекты перешли в сигнальное состояние. Если FALSE – то, вычтя из возвращенного значения WAIT_OBJECT_0, мы получим индекс объекта в массиве lpHandles.        

Число в диапазоне от WAIT_ABANDONED_0 до WAIT_ABANDONED_0 + nCount – 1        Если bWaitAll равно TRUE – это означает, что все перешли в сигнальное состояние, но хотя бы один из владевших ими потоков завершился, не сделав объект сигнальным. Если FALSE – то, вычтя из возвращенного значения WAIT_ABANDONED_0,  мы получим индекс объекта в массиве lpHandles, поток, владевший которым, завершился, не сделав его сигнальным.        

WAIT_TIMEOUT        Истек период ожидания        

WAIT_FAILED        Произошла ошибка        

Например, в следующем фрагменте кода программа пытается модифицировать два различных ресурса, разделяемых между потоками.

Code:

var
Handles: array[0..1] of THandle;
Reason: DWORD;
RestIndex: Integer;
 
...
 
Handles[0] := OpenMutex(SYNCHRONIZE, FALSE, 'FirstResource');
Handles[1] := OpenMutex(SYNCHRONIZE, FALSE, 'SecondResource');
// Ждем первого из объектов
Reason := WaitForMultipleObjects(2, @Handles, FALSE, INFINITE);
case Reason of
WAIT_FAILED: RaiseLastWin32Error;
WAIT_OBJECT_0, WAIT_ABANDONED_0:
   begin
     ModifyFirstResource;
     RestIndex := 1;
   end;
WAIT_OBJECT_0 + 1, WAIT_ABANDONED_0 + 1:
   begin
     ModifySecondResource;
     RestIndex := 0;
   end;
// WAIT_TIMEOUT возникнуть не может
end;
// Теперь ожидаем освобождения следующего объекта
if WailForSingleObject(Handles[RestIndex], 
    INFINITE) = WAIT_FAILED then
      RaiseLastWin32Error;
// Дождались, модифицируем оставшийся ресурс.
if RestIndex = 0 then
ModifyFirstResource
else
ModifySecondResource;

Описанную выше технику можно применять, если Вы точно знаете, что задержка ожидания объекта окажется небольшой. В противном случае Ваша программа окажется "замороженной" и не сможет даже перерисовать своё окно. Если период задержки может оказаться значительным, то необходимо дать программе возможность реагировать на сообщения Windows. Выходом может служить использование функций с ограниченным периодом ожидания (и повторный вызов, в случае возврата WAIT_TIMEOUT), либо использование функции:

function MsgWaitForMultipleObjects(
nCount: DWORD;     // количество объектов синхронизации
var pHandles;      // адрес массива объектов
fWaitAll: BOOL;    // Задает, требуется ожидание всех
                    // объектов или любого
dwMilliseconds,    // Период ожидания
dwWakeMask: DWORD  // Тип события, прерывающего ожидание
): DWORD; stdcall;

Главное отличие этой функции от предыдущей – параметр dwWakeMask, который является комбинацией битовых флагов QS_XXX и задает типы сообщений, которые прерывают ожидание функции, независимо от состояния ожидаемых объектов. Например, маска QS_KEY позволяет прервать ожидание при появлении в очереди сообщений WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP или WM_SYSKEYDOWN, а маска QS_PAINT - сообщения WM_PAINT. Полный список значений, допустимых для dwWakeMask имеется в документации по Windows SDK. При появлении в очереди потока, вызвавшего функцию, сообщений, соответствующих заданной маске функция возвращает значение WAIT_OBJECT_0 + nCount. Получив это значение, Ваша программа может обработать его и снова вызвать функцию ожидания. Рассмотрим пример с запуском внешнего приложения. Необходимо, чтобы на время его работы вызывающая программа не реагировала на ввод пользователя, однако её окно должно продолжать перерисовываться.

Code:

procedure TForm1.Button1Click(Sender: TObject);
var
PI: TProcessInformation;
SI: TStartupInfo;
Reason: DWORD;
Msg: TMsg;
begin
// Инициализируем структуру TStartupInfo
FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(SI);
// Запускаем внешнюю программу
Win32Check(CreateProcess(NIL, 'COMMAND.COM', NIL,
   NIL, FALSE, 0, NIL, NIL, SI, PI));
//**************************************************
// Попробуйте заменить нижеприведенный код на строку
// WaitForSingleObject(PI.hProcess, INFINITE);
// и посмотреть, как будет реагировать программа на
// перемещение других окон над её окном
//**************************************************
repeat
   // Ожидаем завершения дочернего процесса или сообщения 
   // перерисовки WM_PAINT
   Reason := MsgWaitForMultipleObjects(1, PI.hProcess, FALSE,
     INFINITE, QS_PAINT);
   if Reason = WAIT_OBJECT_0 + 1 then begin
     // В очереди сообщений появился WM_PAINT – Windows
     // требует обновить окно программы.
     // Удаляем сообщение из очереди
     PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE);
     // И перерисовываем наше окно
     Update;
   end;
   // Повторяем цикл, пока не завершится дочерний процесс
until Reason = WAIT_OBJECT_0;
// Удаляем из очереди накопившиеся там сообщения
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do;
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread)
end;

Если в потоке, вызывающем функции ожидания явно (функцией CreateWindow) или неявно (используя TForm, DDE, COM) создаются окна Windows – поток должен обрабатывать сообщения. Поскольку широковещательные сообщения посылаются всем окнам в системе – поток, не обрабатывающий сообщения может вызвать взаимоблокировку, (система ждет, когда поток обработает сообщение, поток – когда система или другие потоки освободят объект) и привести к зависанию Windows. Если в Вашей программе имеются подобные фрагменты – необходимо использовать MsgWaitForMultipleObjects или MsgWaitForMultipleObjectsEx и позволять прервать ожидание для обработки сообщений. Алгоритм аналогичен вышеприведенному примеру.

Прерывание ожидания по запросу на завершение операции ввода-вывода или APC

Windows поддерживает асинхронные вызовы процедур. При создании каждого потока (thread) с ним ассоциируется очередь асинхронных вызовов процедур (APC queue). Операционная система (или приложение пользователя, при помощи функции QueueUserAPC) может помещать в неё запросы на выполнение функций в контексте этого потока. Эти функции не могут быть выполнены немедленно, поскольку поток может быть занят. Поэтому, операционная система вызывает их, когда поток вызывает одну из следующих функций ожидания:

function SleepEx(
dwMilliseconds: DWORD;   // Период ожидания
bAlertable: BOOL         // задает, длжна ли функция возвращать
                          // управление в случае запроса на 
                          // асинхронный вызов процедуры
): DWORD; stdcall;
 
function WaitForSingleObjectEx(
hHandle: THandle;      // Идентификатор объекта
dwMilliseconds: DWORD; // Период ожидания
bAlertable: BOOL       // задает, длжна ли функция возвращать
                        // управление в случае запроса на 
                        // асинхронный вызов процедуры
): DWORD; stdcall;
 
function WaitForMultipleObjectsEx(
nCount: DWORD;            // количество объектов
lpHandles: PWOHandleArray;// адрес массива идентификаторов объектов
bWaitAll: BOOL;           // Задает, требуется ожидание всех
                           // объектов или любого
dwMilliseconds: DWORD;    // Период ожидания
bAlertable: BOOL          // задает, должна ли функция возвращать
                           // управление в случае запроса на 
                           // асинхронный вызов процедуры
): DWORD; stdcall;
 
function SignalObjectAndWait(
hObjectToSignal: THandle;  // объект, который будет переведен в
                            // сигнальное состояние 
hObjectToWaitOn: THandle;  // объект, которого ожидает функция
dwMilliseconds: DWORD;     // период ожидания
bAlertable: BOOL           // задает, должна ли функция возвращать
                            // управление в случае запроса на 
                            // асинхронный вызов процедуры
): DWORD; stdcall;
 
function MsgWaitForMultipleObjectsEx(
nCount: DWORD;     // количество объектов синхронизации
var pHandles;      // адрес массива объектов
fWaitAll: BOOL;    // Задает, требуется ожидание всех
                    // объектов или любого
dwMilliseconds,    // Период ожидания
dwWakeMask: DWORD  // Тип события, прерывающего ожидание
dwFlags: DWORD     // Дополнительные флаги
): DWORD; stdcall;

Если параметр bAlertable равен TRUE (либо dwFlags в функции MsgWaitForMultipleObjectsEx содержит MWMO_ALERTABLE) , то при появлении в очереди APC запроса на асинхронный вызов процедуры операционная система выполняет вызовы всех имеющихся в очереди процедур, после чего функция возвращает значение WAIT_IO_COMPLETION.

Такой механизм позволяет реализовать, например, асинхронный ввод-вывод. Поток может инициировать фоновое выполнение одной или нескольких операций ввода-вывода функциями ReadFileEx или WriteFileEx, передав им адреса функций-обработчиков завершения операции. По завершении вызовы этих функций будут поставлены в очередь асинхронного вызова процедур. В свою очередь, инициировавший операции поток, когда он будет готов обработать результаты, может, используя одну из вышеприведенных функций ожидания, позволить операционной системе вызвать функции-обработчики. Поскольку очередь APC реализована на уровне ядра ОС, она более эффективна, чем очередь сообщений и позволяет реализовать гораздо более эффективный ввод-вывод.

Объекты синхронизации

Объектами синхронизации называются объекты Windows, идентификаторы которых могут использоваться в функциях синхронизации. Они делятся на две группы – объекты, использующиеся только для синхронизации и объекты, которые используются в других целях, но могут вызывать срабатывание функций ожидания. К первой группе относятся:

Event (событие)

Позволяет известить один или несколько ожидающих потоков о наступлении события. Event бывает

Отключаемый вручную        Будучи установленным в сигнальное состояние, остается в нем до тех пор, пока не будет переключен явным вызовом функции ResetEvent        

Автоматически отключаемый        Автоматически переключается в несигнальное состояние операционной системой, когда один из ожидающих его потоков завершается.        

Для создания объекта используется функция:

function CreateEvent(
lpEventAttributes: PSecurityAttributes;  // Адрес структуры
                                          // TSecurityAttributes
  bManualReset,         // Задает, будет Event переключаемым 
                        // вручную (TRUE) или автоматически (FALSE)
  bInitialState: BOOL;  // Задает начальное состояние. Если TRUE - 
                        // объект в сигнальном состоянии
  lpName: PChar         // Имя или NIL, если имя не требуется
): THandle; stdcall;     // Возвращает идентификатор созданного
                        // объекта

Структура TSecurityAttributes описана, как:

TSecurityAttributes = record
nLength: DWORD;                // Размер структуры, должен 
                                // инициализироваться как 
                                // SizeOf(TSecurityAttributes)
lpSecurityDescriptor: Pointer; // Адрес дескриптора защиты. В 
                                // Windows 95 и 98 игнорируется
                                // Обычно можно указывать NIL
bInheritHandle: BOOL;          // Задает, могут ли дочерние 
                                // процессы наследовать объект
end;

Если не требуется задание особых прав доступа под Windows NT или возможности наследования объекта дочерними процессами, в качестве параметра lpEventAttributes можно передаseвать NIL. В этом случае объект не может наследоваться дочерними процессами и ему задается дескриптор защиты «по умолчанию».

Параметр lpName позволяет разделять объекты между процессами. Если lpName совпадает с именем уже существующего объекта типа Event, созданного текущим или любым другим процессом, функция не создает нового объекта, а возвращает идентификатор уже существующего. При этом игнорируются параметры bManualReset, bInitialState и lpSecurityDescriptor. Проверить, был объект создан, или используется уже существующий можно следующим образом:

hEvent := CreateEvent(NIL, TRUE, FALSE, 'EventName');
if hEvent = 0 then
RaiseLastWin32Error;
if GetLastError = ERROR_ALREADY_EXISTS then begin
// Используем ранее созданный объект
end;

Если объект используется для синхронизации внутри одного процесса, его можно объявить как глобальную переменную и создавать без имени.

Имя объекта не должно совпадать с именем любого из существующих объектов типов Semaphore, Mutex, Job, Waitable Timer или FileMapping. В случае совпадения имен, функция возвращает ошибку.

Если известно, что Event уже создан, для получения доступа к нему можно вместо CreateEvent воспользоваться функцией:

 

function OpenEvent(

dwDesiredAccess: DWORD;  // Задает права доступа к объекту

bInheritHandle: BOOL;    // Задает, может ли объект наследоваться 

                          // дочерними процессами

lpName: PChar            // Имя объекта

): THandle; stdcall;

Функция возвращает идентификатор объекта, либо 0, в случае ошибки. Параметр dwDesiredAccess может принимать одно из следующих значений:

EVENT_ALL_ACCESS        Приложение получает полный доступ к объекту        

EVENT_MODIFY_STATE        Приложение может изменять состояние объекта функциями SetEvent и ResetEvent        

SYNCHRONIZE        Только для Windows NT – приложение может использовать объект только в функциях ожидания        

После получения идентификатора можно приступать к его использованию. Для этого имеются следующие функции:

function SetEvent(hEvent: THandle): BOOL; stdcall;

Устанавливает объект в сигнальное состояние

function ResetEvent(hEvent: THandle): BOOL; stdcall;

Сбрасывает объект, устанавливая его в несигнальное состояние

function PulseEvent(hEvent: THandle): BOOL; stdcall

Устанавливает объект в сигнальное состояние, дает отработать всем функциям ожидания, ожидающим этот объект, а затем снова сбрасывает его.

В WinAPI события используются, для выполнения операций асинхронного ввода-вывода. Следующий пример показывает, как приложение инициирует запись одновременно в два файла, а затем ожидает завершения записи перед продолжением работы. Такой подход может обеспечить более высокую производительность при высокой интенсивности ввода-вывода, чем последовательная запись.

Code:

var
Events: array[0..1] of THandle;  // Массив объектов синхронизации
Overlapped: array[0..1] of TOverlapped; 
 
...
 
// Создаем объекты синхронизации
Events[0] := CreateEvent(NIL, TRUE, FALSE, NIL);
Events[1] := CreateEvent(NIL, TRUE, FALSE, NIL);
 
// Инициализируем структуры TOverlapped
FillChar(Overlapped, SizeOf(Overlapped), 0);
Overlapped[0].hEvent := Events[0];
Overlapped[1].hEvent := Events[1];
 
// Начинаем асинхронную запись в файлы
WriteFile(hFirstFile, FirstBuffer, SizeOf(FirstBuffer),
FirstFileWritten, @Overlapped[0]);
WriteFile(hSecondFile, SecondBuffer, SizeOf(SecondBuffer),
SecondFileWritten, @Overlapped[1]);
 
// Ожидаем завершения записи в оба файла
WaitForMultipleObjects(2, @Events, TRUE, INFINITE);
 
// Уничтожаем объекты синхронизации
CloseHandle(Events[0]);
CloseHandle(Events[1]);

По завершении работы с объектом, он должен быть уничтожен функцией CloseHandle.

Delphi предоставляет класс TEvent, инкапсулирующий функциональность объекта Event. Класс расположен в модуле SyncObjs.pas и объявлен следующим образом:

Code:

type
TWaitResult = (wrSignaled, wrTimeout, wrAbandoned, wrError);
 
TEvent = class(THandleObject)
public
   constructor Create(EventAttributes: PSecurityAttributes;
     ManualReset, InitialState: Boolean; const Name: string);
   function WaitFor(Timeout: DWORD): TWaitResult;
   procedure SetEvent;
   procedure ResetEvent;
end;

Назначение методов очевидно из их названий. Использование этого класса позволяет не вдаваться в тонкости реализации вызываемых функций Windows API. Для простейших случаев объявлен еще один класс с упрощенным конструктором.

Code:

type
TSimpleEvent = class(TEvent)
public
   constructor Create;
end;
 

 
constructor TSimpleEvent.Create;
begin
FHandle := CreateEvent(nil, True, False, nil);
end;

Mutex (Mutually Exclusive)

Мутекс – это объект синхронизации, который находится в сигнальном состоянии только тогда, когда он не принадлежит ни одному из процессов. Как только хотя бы один процесс запрашивает владение мутексом, он переходит в несигнальное состояние и остается в нем до тех пор, пока не будет освобожден владельцем. Такое поведение позволяет использовать мутексы для синхронизации совместного доступа нескольких процессов к разделяемому ресурсу. Для создания мутекса используется функция:

function CreateMutex(
lpMutexAttributes: PSecurityAttributes;  // Адрес структуры 
                                          // TSecurityAttributes
bInitialOwner: BOOL;  // Задает, будет ли процесс владеть
                       // мутексом сразу после создания
lpName: PChar         // Имя мутекса
): THandle; stdcall;

Функция возвращает идентификатор созданного объекта, либо 0. Если мутекс с заданным именем уже был создан, возвращается его идентификатор. В этом случае функция GetLastError вернет код ошибки ERROR_ALREDY_EXISTS. Имя не должно совпадать с именем уже существующего объекта типов Semaphore, Event, Job, Waitable Timer или FileMapping

Если неизвестно, существует ли уже мутекс с таким именем, программа не должна запрашивать владение объектом при создании (т.е. должна передать в качестве bInitialOwner значение FALSE).

Если мутекс уже существует, приложение может получить его идентификатор функцией

function OpenMutex(
dwDesiredAccess: DWORD;  // Задает права доступа к объекту
bInheritHandle: BOOL;    // Задает, может ли объект наследоваться 
                          // дочерними процессами
lpName: PChar            // Имя объекта
): THandle; stdcall;

Параметр dwDesiredAccess может принимать одно из следующих значений

MUTEX_ALL_ACCESS        Приложение получает полный доступ к объекту        

SYNCHRONIZE        Только для Windows NT – приложение может использовать объект только в функциях ожидания и функции ReleaseMutex        

Функция возвращает идентификатор открытого мутекса, либо 0, в случае ошибки. Мутекс переходит в сигнальное состояние после срабатывания функции ожидания, в которую был передан его идентификатор. Для возврата в несигнальное состояние служит функция

function ReleaseMutex(hMutex: THandle): BOOL; stdcall;

Если несколько процессов обмениваются данными, например, через файл, отображенный на память, каждый из них должен содержать следующий код для обеспечения корректного доступа к общему ресурсу:

Code:

var
Mutex: THandle;
 
// При инициализации программы
Mutex := CreateMutex(NIL, FALSE, 'UniqueMutexName');
if Mutex = 0 then
RaiseLastWin32Error;
 
...
// Доступ к ресурсу
WaitForSingleObject(Mutex, INFINITE);
try
// Доступ к ресурсу, захват мутекса гарантирует,
// что остальные процессы пытающиеся получить доступ
// будут остановлены на функции WaitForSingleObject
...
finally
// Работа с ресурсом окончена, освобождаем его
// для остальных процессов
ReleaseMutex(Mutex);
end;
 
...
// При завершении программы
CloseHandle(Mutex);

Подобный код удобно инкапсулировать в класс, который создает защищенный ресурс, мутекс, имеет свойства и методы для оперирования ресурсом, защищая их при помощи функций синхронизации.

Разумеется, если работа с ресурсом может потребовать значительного времени, то необходимо либо использовать функцию MsgWaitForSingleObject, либо вызывать WaitForSingleObject в цикле с нулевым периодом ожидания, проверяя код возврата. В противном случае Ваше приложение окажется замороженным. Всегда защищайте захват-освобождение объекта синхронизации при помощи блока try ... finally, иначе ошибка во время работы с ресурсом приведет к блокированию работы всех процессов, ожидающих его освобождения.

Semaphore (семафор)

Семафор представляет собой счетчик, содержащий целое число в диапазоне от 0 до заданной при его создании максимальной величины. Счетчик уменьшается каждый раз, когда поток успешно завершает функцию ожидания, использующую семафор и увеличивается вызовом функции ReleaseSemaphore. При достижении семафором значения 0 он переходит в несигнальное состояние, при любых других значениях счетчика – его состояние сигнальное. Такое поведение позволяет использовать семафор в качестве ограничителя доступа к ресурсу, поддерживающему заранее заданное количество подключений.

Для создания семафора служит функция:

function CreateSemaphore(
lpSemaphoreAttributes: PSecurityAttributes; // Адрес структуры 
                                             // TSecurityAttributes
lInitialCount,           // Начальное значение счетчика
lMaximumCount: Longint;  // Максимальное значение счетчика
lpName: PChar            // Имя объекта
): THandle; stdcall;

Функция возвращает идентификатор созданного семафора, либо 0, если создать объект не удалось.

Параметр lMaximumCount задает максимальное значение счетчика семафора, lInitialCount задает начальное значение счетчика и должен быть в диапазоне от 0 до lMaximumCount. lpName задает имя семафора. Если в системе уже есть семафор с таким именем, то новый не создается, а возвращается идентификатор существующего семафора. В случае если семафор используется внутри одного процесса, можно создать его без имени, передав в качестве lpName значение NIL. Имя семафора не должно совпадать с именем уже существующего объекта типов event, mutex, waitable timer, job, или file-mapping.

Идентификатор ранее созданного семафора может быть, также, получен функцией:

function OpenSemaphore(
dwDesiredAccess: DWORD;  // Задает права доступа к объекту
bInheritHandle: BOOL;    // Задает, может ли объект наследоваться 
                          // дочерними процессами
lpName: PChar            // Имя объекта
): THandle; stdcall;

Параметр dwDesiredAccess может принимать одно из следующих значений:

SEMAPHORE_ALL_ACCESS        Поток получает все права на семафор        

SEMAPHORE_MODIFY_STATE        Поток может увеличивать счетчик семафора функцией ReleaseSemaphore        

SYNCHRONIZE        Только Windows NT – поток может использовать семафор в функциях ожидания        

Для увеличения счетчика семафора используется функция:

function ReleaseSemaphore(
hSemaphore: THandle;      // Идентификатор семафора
lReleaseCount: Longint;   // Счетчик будет увеличен на эту величину
lpPreviousCount: Pointer  // Адрес 32-битной переменной, 
                           // принимающей предыдущее значение
                           // счетчика
): BOOL; stdcall;

Если значение счетчика после выполнения функции превысит заданный для него функцией CreateSemaphore максимум, то ReleaseSemaphore возвращает FALSE и значение семафора не изменяется. В качестве параметра lpPreviousCount можно передать NIL, если это значение нам не нужно.

Рассмотрим пример приложения, запускающего на выполнение несколько заданий в отдельных потоках (например, программа для фоновой загрузки файлов из Internet). Если количество одновременно выполняющихся заданий будет слишком велико, то это приведет к неоправданной загрузке канала. Поэтому реализуем потоки, в которых будет выполняться задание таким образом, чтобы при превышении их количества заранее заданной величины поток останавливался и ожидал завершения работы ранее запущенных заданий.

Code:

unit LimitedThread;
 
interface
 
uses Classes;
 
type
TLimitedThread = class(TThread)
   procedure Execute; override;
end;
 
implementation
 
uses Windows;
 
const
MAX_THREAD_COUNT = 10;
 
var
Semaphore: THandle;
 
procedure TLimitedThread.Execute;
begin
// Уменьшаем счетчик семафора. Если к этому моменту уже запущено
// MAX_THREAD_COUNT потоков – счетчик равен 0 и семафор в 
// несигнальном состоянии. Поток будет заморожен до завершения 
// одного из запущенных ранее.
WaitForSingleObject(Semaphore, INFINITE);
 
// Здесь располагается код, отвечающий за функциональность потока,
// например загрузка файла
...
 
// Поток завершил работу, увеличиваем счетчик семафора и позволяем
// начать обработку другим потокам.
ReleaseSemaphore(Semaphore, 1, NIL);
end;
 
initialization
// Создаем семафор при старте программы
Semaphore := CreateSemaphore(NIL, MAX_THREAD_COUNT, 
   MAX_THREAD_COUNT, NIL);
 
finalization
// Уничтожаем семафор по завершении программы
CloseHandle(Semaphore);
end;

Дополнительные объекты синхронизации

Некоторые объекты Win32 API не предназначены исключительно для целей синхронизации, однако могут использоваться с функциями синхронизации. Такими объектами являются:

Сообщение об изменении папки (change notification)

Windows позволяет организовать слежение за изменениями объектов файловой системы. Для этого служит функция

function FindFirstChangeNotification(
lpPathName: PChar;     // Путь к папке, изменения в которой нас 
                        // интересуют
bWatchSubtree: BOOL;   // Задает необходимость слежения за 
                        // изменениями во вложенных папках
dwNotifyFilter: DWORD  // Фильтр событий
): THandle; stdcall;

Параметр dwNotifyFilter – это битовая маска из одного или нескольких следующих значений:

FILE_NOTIFY_CHANGE_FILE_NAME        

       Слежение ведется за любым изменением имени файла, в т.ч. созданием и удалением файлов        

FILE_NOTIFY_CHANGE_DIR_NAME        

       Слежение ведется за любым изменением имени папки, в т.ч. созданием и удалением папок        

FILE_NOTIFY_CHANGE_ATTRIBUTES        

       Слежение ведется за любым изменением аттрибутов        

FILE_NOTIFY_CHANGE_SIZE        

       Слежение ведется за изменением размера файлов. Изменение размера происходит при записи в файл. Функция ожидания срабатывает только после успешного сброса дискового кэша        

FILE_NOTIFY_CHANGE_LAST_WRITE        

       Слежение за изменением времени последней записи в файл, т.е., фактически, за любой записью в файл. Функция ожидания срабатывает только после успешного сброса дискового кэша.        

FILE_NOTIFY_CHANGE_SECURITY        

       Слежение за любыми изменениями дескрипторов защиты        

Идентификатор, возвращенный этой функцией, может использоваться в любой функции ожидания. Он переходит в сигнальное состояние, когда в папке происходят запрошенные для слежения изменения. Продолжить слежение можно, используя функцию:

function FindNextChangeNotification(

hChangeHandle: THandle

): BOOL; stdcall;

По завершении работы, идентификатор должен быть закрыт при помощи функции:

function FindCloseChangeNotification(

hChangeHandle: THandle

): BOOL; stdcall;

Чтобы не блокировать исполнение основного потока программы функцией ожидания, удобно реализовать ожидание изменений в отдельном потоке. Реализуем поток на базе класса TThread. Для того чтобы можно было прервать исполнение потока методом Terminate необходимо, чтобы функция ожидания, реализованная в методе Execute, также прерывалась при вызове Terminate. Для этого будем использовать вместо WaitForSingleObject функцию WaitForMultipleObjects, и прерывать ожидание по событию (event), устанавливаемому в Terminate.

Code:

type
TCheckFolder = class(TThread)
private
   FOnChange: TNotifyEvent;
   Handles: array[0..1] of THandle;  // Идентификаторы объектов
                                     // синхронизации
   procedure DoOnChange;
protected
   procedure Execute; override;
public
   constructor Create(CreateSuspended: Boolean;
     PathToMonitor: String; WaitSubTree: Boolean;
     OnChange: TNotifyEvent; NotifyFilter: DWORD);
   destructor Destroy; override;
   procedure Terminate;
end;
 
procedure TCheckFolder.DoOnChange;
// Эта процедура вызывается в контексте главного потока приложения
// В ней можно использовать вызовы VCL, изменять состояние формы,
// например перечитать содержимое TListBox, отображающего файлы
begin
if Assigned(FOnChange) then
   FOnChange(Self);
end;
 
procedure TCheckFolder.Terminate;
begin
inherited; // Вызываем TThread.Terminate, устанавливаем 
            // Terminated = TRUE
SetEvent(Handles[1]);  // Сигнализируем о необходимости
                        // прервать ожидание
end;
 
constructor TCheckFolder.Create(CreateSuspended: Boolean;
     PathToMonitor: String; WaitSubTree: Boolean;
     OnChange: TNotifyEvent; NotifyFilter: DWORD);
var
BoolForWin95: Integer;
begin
// Создаем поток остановленным
inherited Create(TRUE);
// Windows 95 содержит не очень корректную реализацию функции
// FindFirstChangeNotification. Для корректной работы, необходимо,
// чтобы:
// - lpPathName - не содержал завершающего слэша "" для
//                некорневого каталога
// - bWatchSubtree - TRUE должен передаваться как BOOL(1)
if WaitSubTree then
   BoolForWin95 := 1
else
   BoolForWin95 := 0;
if (Length(PathToMonitor) > 1) and
    (PathToMonitor[Length(PathToMonitor)] = '') and
    (PathToMonitor[Length(PathToMonitor)-1] <> ':') then
    Delete(PathToMonitor, Length(PathToMonitor), 1);
Handles[0] := FindFirstChangeNotification(
   PChar(PathToMonitor), BOOL(BoolForWin95), NotifyFilter);
Handles[1] := CreateEvent(NIL, TRUE, FALSE, NIL);
FOnChange := OnChange;
// И, при необходимости, запускаем
if not CreateSuspended then
   Resume;
end;
 
destructor TCheckFolder.Destroy;
begin
FindCloseChangeNotification(Handles[0]);
CloseHandle(Handles[1]); 
inherited;
end;
 
procedure TCheckFolder.Execute;
var
Reason: Integer;
Dummy: Integer;
begin
repeat
   // Ожидаем изменения в папке, либо сигнала о завершении 
   // потока
   Reason := WaitForMultipleObjects(2, @Handles, FALSE, INFINITE);
   if Reason = WAIT_OBJECT_0 then begin
     // Изменилась папка, вызываем обработчик в контексте
     // главного потока приложения
     Synchronize(DoOnChange);
     // И продолжаем поиск
     FindNextChangeNotification(Handles[0]);
   end;
until Terminated;
end;

Поскольку метод TThread.Terminate не виртуальный, этот класс нельзя использовать с переменной типа TThread, т.к. в этом случае будет вызываться Terminate от TThread, который не может прервать ожидания, и поток будет выполняться до изменения в папке, за которой ведется слежение.

Устройство стандартного ввода с консоли (console input)

Идентификатор, стандартного устройства ввода с консоли, полученный при помощи вызова функции GetStdHandle(STD_INPUT_HANDLE), можно использовать в функциях ожидания. Он находится в сигнальном состоянии, если очередь ввода консоли непустая и в несигнальном, если пустая. Это позволяет организовать ожидание ввода символов, либо, при помощи функции WaitForMultipleObjects совместить его с ожиданием каких-то других событий.

Задание (Job)

Job – это новый механизм Windows 2000, позволяющий объединить группу процессов в одно задание и манипулировать ими одновременно. Идентификатор задания находится в сигнальном состоянии, если все процессы, ассоциированные с ним завершились по причине истечения лимита времени на выполнение задания.

Процесс (Process)

Идентификатор процесса, полученный при помощи функции CreateProcess, переходит в сигнальное состояние по завершении процесса. Это позволяет организовать ожидание завершения процесса, например, при запуске из приложения внешней программы.

var
PI: TProcessInformation;
SI: TStartupInfo;
 
...
 
FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(SI);
Win32Check(CreateProcess(NIL, 'COMMAND.COM', NIL,
   NIL, FALSE, 0, NIL, NIL, SI, PI));
// Задерживаем исполнение программы до завершения процесса
WaitForSingleObject(PI.hProcess, INFINITE);
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);

Следует понимать, что в этом случае вызывающий процесс будет заморожен полностью и не сможет обрабатывать сообщения. Поэтому, если дочерний процесс может выполняться в течение длительного времени, лучше использовать более корректный вариант ожидания, описанный в разделе, посвященном функции MsgWaitForMultipleObjects.

Поток (thread)

Идентификатор потока находится в несигнальном состоянии до тех пор, пока поток выполняется. По его завершении идентификатор переходит в сигнальное состояние. Это позволяет легко узнать, завершился ли поток, либо при помощи функции, ожидающей нескольких объектов, организовать ожидание завершения одного из, либо всех интересующих потоков.

Дополнительные механизмы синхронизации
  1   2


написать администратору сайта