Что такое семафор в windows

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

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

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

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

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

   Event (событие)

   Mutex (Mutually Exclusive)

   Semaphore (семафор)

   Waitable timer (таймер ожидания)

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

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

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

      Задание (Job)

      Процесс (Process)

      Поток (thread)

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

   Критические секции

   Защищенный доступ к переменным (Interlocked Variable Access)

Резюме

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

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

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

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

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

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

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

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):

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; 

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

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 
Произошла ошибка

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

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)
либо функции MsgWaitForMultipleObjects:

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.
Получив это значение, ваша программа может
обработать его и снова вызвать функцию
ожидания. Рассмотрим пример с запуском
внешнего приложения (необходимо, чтобы на время его работы
вызывающая программа не реагировала на
ввод пользователя, однако ее окно должно
продолжать перерисовываться):

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
позволяет
известить один или несколько ожидающих
потоков о наступлении события. Event
бывает:

Отключаемый
вручную

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

Автоматически
отключаемый

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

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

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
можно передавать 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 воспользоваться функцией OpenEvent:

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  

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

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

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 и объявлен следующим образом:

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. Для простейших случаев объявлен еще один
класс с упрощенным конструктором:

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).

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

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

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

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

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

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

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

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 он
переходит в несигнальное состояние, при
любых других значениях счетчика его
состояние — сигнальное. Такое
поведение позволяет использовать семафор в
качестве ограничителя доступа к ресурсу,
поддерживающему заранее заданное
количество подключений.

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

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.

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

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

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

SEMAPHORE_ALL_ACCESS              

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

SEMAPHORE_MODIFY_STATE              

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

SYNCHRONIZE              

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

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

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

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

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

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;  

Provide feedback

Saved searches

Use saved searches to filter your results more quickly

Sign up

Appearance settings

Для программ, использующих несколько потоков или процессов, необходимо, чтобы все они выполняли возложенные на них функции в нужной последовательности. В среде Windows 9x для этой цели предлагается использовать несколько механизмов, обеспечивающих слаженную работу потоков. Эти механизмы называют механизмами синхронизации.

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

—         первый поток прочитал значение глобальной переменной в локальную;

—         ОС прерывает его, так как закончился выделенный ему квант времени процессора, и передаёт управление второму потоку;

—         второй поток также считал значение глобальной переменной в локальную, декрементировал её и записал новое значение обратно;

—         ОС вновь передаёт управление первому потоку, тот, ничего не зная о действиях второго потока, инкрементирует свою локальную переменную и  записывает её значение в глобальную.

Очевидно, что изменения, внесённые вторым потоком, будут утеряны.

Для исключения подобных ситуаций необходимо разделить во времени использование совместных данных. В таких случаях используются механизмы синхронизации, которые обеспечивают корректную работу нескольких потоков.

Средства синхронизации в ОС Windows:

1) критическая секция (Critical Section) – это объект, который принадлежи процессу, а не ядру. А значит, не может синхронизировать потоки из разных процессов.

Существует так же функции инициализации (создания) и удаления, вхождения и выхода из критической секции:

создание – InitializeCriticalSection(…),    удаление – DeleteCriticalSection(…),

вход – EnterCriticalSection(…),                выход – LeaveCriticalSection(…).

Ограничения: поскольку это не объект ядра, то он не виден другим процессам, то есть можно защищать только потоки своего процесса.

Критический раздел анализирует значение специальной перемен­ной процесса, которая используется как флаг, предотвращающий исполнение некоторого участка кода несколькими потоками одновременно.

Среди синхронизирующих объектов критические разделы наиболее просты.

2) mutexmutable exclude. Это объект ядра, у него есть имя, а значит с их помощью можно синхронизировать доступ к общим данным со стороны нескольких процессов, точнее, со стороны потоков разных процессов. Ни один другой поток не может завладеть мьютексом, который уже принадлежит одному из по­токов. Если мьютекс защищает какие-то совместно используемые данные, он сможет выполнить свою функцию только в случае, если перед обращением к этим данным каждый из потоков будет проверять состояние этого мьютекса. Windows расценивает мьютекс как объект общего доступа, который можно пере­вести в сигнальное состояние или сбросить. Сигнальное состояние мьютекса говорит о том, что он занят. Потоки должны самостоятельно ана­лизировать текущее состояние мьютексов. Если требуется, чтобы к мьютексу могли обратиться потоки других процессов, ему надо присвоить имя.

Функции:

CreateMutex(имя) – создание,                  hnd=OpenMutex(имя) – открытие,

WaitForSingleObject(hnd) – ожидание и занятие,

ReleaseMutex(hnd) – освобождение,        CloseHandle(hnd) – закрытие.

Его можно использовать в защите от повторного запуска программ.

3) семафор – semaphore. Объект ядра “семафор” используются для учёта ресурсов и служат для ограничения одновременного доступа к ресурсу нескольких потоков. Используя семафор, можно организовать работу программы таким образом, что к ресурсу одновременно смо­гут получить доступ несколько потоков, однако количество этих потоков будет ограничено. Создавая семафор, указывается максимальное количество пото­ков, которые одновременно смогут работать с ресурсом. Каждый раз, когда программа обращается к семафору, значение счетчика ресурсов семафора уменьша­ется на единицу. Когда значение счетчика ресурсов становится равным нулю, семафор недоступен.

создание CreateSemaphore,                      открытие OpenSemaphore,

занять WaitForSingleObject,                     освобождение ReleaseSemaphore

4событие – event. События обычно просто оповещают об окончании какой-либо операции, они также являются объектами ядра. Можно не просто явным образом освободить, но так же есть операция установки события. События могут быть мануальными (manual) и единичными (single).

Единичное событие (single event) – это скорее общий флаг. Событие находится в сигнальном состоянии, если его установил какой-нибудь поток. Если для работы программы требуется, чтобы в случае возникновения события на него реа­гировал только один из потоков, в то время как все остальные потоки продолжали ждать, то используют единичное событие.

Мануальное событие (manual event) — это не про­сто общий флаг для нескольких потоков. Оно выполняет несколько более сложные функции. Любой поток может установить это событие или сбросить (очистить) его. Если событие установлено, оно останется в этом состоянии сколь угодно долгое время, вне зависимости от того, сколько потоков ожидают установки этого события. Когда все потоки, ожидающие этого события, получат сообщение о том, что событие произошло, оно автоматически сбросится.

Функции: SetEvent, ClearEvent, WaitForEvent.

Типы событий:

1) событие с автоматическим сбросом: WaitForSingleEvent.

2) событие с ручным сбросом (manual), тогда событие необходимо сбрасывать: ReleaseEvent.

Некоторые теоретики выделяют ещё один объект синхронизации:

WaitAbleTimer – объект ядра ОС, который самостоятельно переходит в свободное состояние через заданный интервал времени (будильник).

2024-12-03

Введение

Управление процессами в Windows API (WinAPI) включает в себя создание, управление и взаимодействие с процессами и потоками в операционной системе Windows. Ниже приведены основные функции и концепции, используемые для работы с процессами в WinAPI:

  1. Создание процессов:
    • CreateProcess: Функция, которая создает новый процесс. Вы можете указать исполняемый файл, командную строку и другие параметры при создании процесса.
  2. Информация о процессе:
    • GetCurrentProcess: Функция, которая возвращает дескриптор текущего процесса.
    • OpenProcess: Функция, которая открывает существующий процесс по его идентификатору (PID).
    • GetProcessId: Функция, которая возвращает идентификатор процесса для данного дескриптора процесса.
  3. Управление приоритетом процессов:
    • SetPriorityClass: Функция, которая устанавливает приоритет выполнения процесса.
    • GetPriorityClass: Функция, которая возвращает текущий приоритет процесса.
  4. Управление потоками внутри процесса:
    • CreateThread: Функция, которая создает новый поток внутри процесса.
    • TerminateThread: Функция, которая завершает выполнение потока.
  5. Синхронизация между потоками и процессами:
    • Mutex, Semaphore, Event: Различные объекты синхронизации, которые можно использовать для синхронизации работы процессов и потоков.
  6. Завершение процессов:
    • ExitProcess: Функция, которая завершает текущий процесс.
    • TerminateProcess: Функция, которая принудительно завершает другой процесс.
  7. Получение информации о процессах:
    • EnumProcesses: Функция, которая позволяет перечислить все запущенные процессы и получить их идентификаторы.
    • QueryFullProcessImageName: Функция, которая возвращает путь к исполняемому файлу процесса по его идентификатору.
  8. Модули процесса:
    • EnumProcessModules: Функция, которая позволяет перечислить все модули (библиотеки DLL) внутри процесса.
  9. Управление разрешениями и защитой процессов:
    • OpenProcessToken, GetTokenInformation: Функции для работы с маркерами доступа процессов и получения информации о правах доступа.

Создание процессов

Создание процессов в операционной системе Windows с использованием Windows API (WinAPI) осуществляется с помощью функции CreateProcess. Эта функция позволяет запустить новый процесс и настроить различные атрибуты его выполнения. Вот пример использования функции CreateProcess на языке C++:

#include <windows.h>
#include <tchar.h>

int main() {
    SetConsoleOutputCP(1251);
    // Имя исполняемого файла, который нужно запустить
    LPCTSTR applicationName = _T("C:\\Path\\To\\Your\\Program.exe");

    // Командная строка для передачи процессу
    LPTSTR commandLine = NULL;

    // Защитные атрибуты процесса и потока (обычно NULL)
    LPSECURITY_ATTRIBUTES processAttributes = NULL;
    LPSECURITY_ATTRIBUTES threadAttributes = NULL;

    // Флаги создания процесса
    BOOL inheritHandles = FALSE;
    DWORD creationFlags = 0;
    LPVOID environment = NULL;
    LPCTSTR currentDirectory = NULL;
    STARTUPINFO startupInfo;
    PROCESS_INFORMATION processInfo;

    // Заполнение структуры STARTUPINFO
    ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
    startupInfo.cb = sizeof(STARTUPINFO);

    // Создание нового процесса
    BOOL result = CreateProcess(
        applicationName,     // Имя исполняемого файла
        commandLine,         // Командная строка
        processAttributes,   // Атрибуты процесса
        threadAttributes,    // Атрибуты потока
        inheritHandles,      // Флаг наследования дескрипторов
        creationFlags,       // Флаги создания процесса
        environment,         // Переменные окружения (обычно NULL)
        currentDirectory,    // Текущий рабочий каталог (обычно NULL)
        &startupInfo,        // Структура STARTUPINFO
        &processInfo         // Структура PROCESS_INFORMATION
    );

    if (result) {
        // Процесс успешно создан
        // Вы можете получить информацию о процессе, используя processInfo

        // Закрыть дескрипторы, чтобы избежать утечек ресурсов
        CloseHandle(processInfo.hProcess);
        CloseHandle(processInfo.hThread);
    } else {
        // Произошла ошибка при создании процесса
        DWORD error = GetLastError();
        // Обработка ошибки
    }

    return 0;
}

Обратите внимание, что вы должны заменить C:\\Path\\To\\Your\\Program.exe на путь к исполняемому файлу, который вы хотите запустить. Функция CreateProcess возвращает TRUE, если процесс успешно создан, и FALSE, если произошла ошибка. В случае ошибки, вы можете использовать GetLastError для получения кода ошибки и дальнейшей обработки.

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

Функция CreateProcess в Windows API имеет множество параметров для настройки создания нового процесса. Вот описание основных параметров:

  1. lpApplicationName (тип: LPCTSTR):
    • Имя исполняемого файла, который нужно запустить. Этот параметр может быть NULL, если имя исполняемого файла включено в строку командной строки.
  2. lpCommandLine (тип: LPTSTR):
    • Командная строка, передаваемая запускаемому процессу. Этот параметр может содержать имя исполняемого файла и его аргументы. Если lpApplicationName не является NULL, то lpCommandLine будет использоваться для передачи аргументов командной строки. В противном случае, lpCommandLine должен содержать полную команду.
  3. lpProcessAttributes (тип: LPSECURITY_ATTRIBUTES):
    • Атрибуты безопасности процесса. Обычно устанавливаются в NULL для использования атрибутов по умолчанию.
  4. lpThreadAttributes (тип: LPSECURITY_ATTRIBUTES):
    • Атрибуты безопасности потока. Обычно устанавливаются в NULL для использования атрибутов по умолчанию.
  5. bInheritHandles (тип: BOOL):
    • Флаг, указывающий, должны ли дескрипторы открытых файлов и другие ресурсы наследоваться процессом, созданным функцией CreateProcess.
  6. dwCreationFlags (тип: DWORD):
    • Флаги создания процесса, определяющие различные параметры и поведение процесса. Например, вы можете использовать CREATE_NEW_CONSOLE, чтобы создать новое окно консоли для процесса.
  7. lpEnvironment (тип: LPVOID):
    • Указатель на блок переменных окружения, которые будут использоваться в новом процессе. Обычно устанавливается в NULL, чтобы процесс унаследовал текущее окружение.
  8. lpCurrentDirectory (тип: LPCTSTR):
    • Текущий рабочий каталог для нового процесса. Обычно устанавливается в NULL, чтобы процесс использовал текущий рабочий каталог родительского процесса.
  9. lpStartupInfo (тип: LPSTARTUPINFO):
    • Указатель на структуру STARTUPINFO, которая содержит информацию о создаваемом процессе, такую как дескрипторы для ввода, вывода и ошибок.
  10. lpProcessInformation (тип: LPPROCESS_INFORMATION):
    • Указатель на структуру PROCESS_INFORMATION, в которой будут возвращены дескрипторы процесса и потока после успешного создания процесса.

Информация о процессе

Для получения информации о процессе в операционной системе Windows с помощью Windows API (WinAPI) можно использовать несколько функций и структур данных. Вот основные средства для получения информации о процессе:

  1. OpenProcess:
    • Функция OpenProcess позволяет открыть существующий процесс и получить дескриптор процесса (HANDLE), который можно использовать для выполнения различных операций над процессом. Эта функция принимает в качестве параметров идентификатор процесса (PID) и права доступа.
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
  1. GetProcessId:
    • Функция GetProcessId используется для получения идентификатора процесса (PID) по его дескриптору.
DWORD processId = GetProcessId(hProcess);
  1. GetProcessTimes:
    • Функция GetProcessTimes позволяет получить информацию о времени выполнения процесса, включая время начала выполнения и использования ЦП.
FILETIME creationTime, exitTime, kernelTime, userTime;
if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime)) {
    // Обработка информации о времени выполнения процесса
}
  1. GetProcessMemoryInfo:
    • Функция GetProcessMemoryInfo позволяет получить информацию о потреблении памяти процессом.
PROCESS_MEMORY_COUNTERS memInfo;
if (GetProcessMemoryInfo(hProcess, &memInfo, sizeof(memInfo))) {
    // Обработка информации о потреблении памяти процессом
}
  1. QueryFullProcessImageName:
    • Функция QueryFullProcessImageName используется для получения полного пути к исполняемому файлу процесса.
TCHAR processPath[MAX_PATH];
DWORD pathSize = sizeof(processPath) / sizeof(TCHAR);
if (QueryFullProcessImageName(hProcess, 0, processPath, &pathSize)) {
    // Обработка пути к исполняемому файлу процесса
}
  1. CloseHandle:
    • Для освобождения ресурсов, связанных с дескриптором процесса, используйте функцию CloseHandle.

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

Управление приоритетом процессов

Управление приоритетом процессов в операционной системе Windows можно выполнять с помощью Windows API (WinAPI). Приоритет процесса определяет, как операционная система распределяет процессорное время между процессами. Вот основные функции и концепции, связанные с управлением приоритетом процессов:

  1. Установка приоритета процесса:

    Для установки приоритета процесса используется функция SetPriorityClass. Эта функция изменяет приоритет выполнения всего процесса.

    BOOL success = SetPriorityClass(GetCurrentProcess(), priority);

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

    • HIGH_PRIORITY_CLASS: Высокий приоритет.
    • NORMAL_PRIORITY_CLASS: Нормальный приоритет (по умолчанию).
    • IDLE_PRIORITY_CLASS: Низкий приоритет.
    • REALTIME_PRIORITY_CLASS: Реальное время (осторожно при использовании, так как это может привести к зависанию системы).
  2. Получение текущего приоритета процесса:

    Для получения текущего приоритета процесса используется функция GetPriorityClass.

    DWORD priority = GetPriorityClass(GetCurrentProcess());

    Значение priority будет одним из перечисленных выше констант.

  3. Установка приоритета потока:

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

    BOOL success = SetThreadPriority(hThread, priority);

    Здесь hThread — дескриптор потока, а priority — желаемый приоритет потока.

  4. Получение текущего приоритета потока:

    Для получения текущего приоритета потока используется функция GetThreadPriority.

    int priority = GetThreadPriority(hThread);

    Значение priority будет числовым представлением приоритета потока.

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

Управление потоками внутри процесса

Управление потоками внутри процесса в операционной системе Windows можно выполнять с использованием Windows API (WinAPI). Вот основные функции и концепции, связанные с управлением потоками:

  1. Создание потока:

    Для создания нового потока внутри процесса используется функция CreateThread. Эта функция позволяет запустить новый поток и выполнить в нем определенную функцию.

    HANDLE hThread = CreateThread(
        NULL,                   // Атрибуты безопасности потока (обычно NULL)
        0,                      // Размер стека (0 = размер стека по умолчанию)
        ThreadFunction,         // Функция, которая будет выполнена в потоке
        lpParam,                // Дополнительные параметры для функции
        0,                      // Флаги создания потока (0 = запуск сразу после создания)
        &dwThreadId             // Идентификатор потока
    );

    Здесь ThreadFunction — это указатель на функцию, которая будет выполняться в потоке, и lpParam — дополнительные параметры, которые могут быть переданы в функцию.

  2. Завершение потока:

    Для завершения выполнения потока используется функция ExitThread. Вызов этой функции приведет к завершению текущего потока.

  3. Ожидание завершения потока:

    Для ожидания завершения выполнения потока используется функция WaitForSingleObject или WaitForMultipleObjects, в зависимости от количества потоков, которые нужно ожидать.

    DWORD dwExitCode;
    DWORD dwWaitResult = WaitForSingleObject(hThread, INFINITE);
    
    if (dwWaitResult == WAIT_OBJECT_0) {
        // Поток завершил выполнение
        GetExitCodeThread(hThread, &dwExitCode);
        // Обработка результата выполнения потока (dwExitCode)
    } else {
        // Обработка ошибки ожидания
    }
  4. Установка приоритета потока:

    Установка приоритета выполнения потока внутри процесса выполняется с помощью функции SetThreadPriority.

    BOOL success = SetThreadPriority(hThread, priority);

    Здесь hThread — дескриптор потока, а priority — желаемый приоритет потока.

  5. Получение текущего приоритета потока:

    Для получения текущего приоритета потока используется функция GetThreadPriority.

    int priority = GetThreadPriority(hThread);
  6. Закрытие дескриптора потока:

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

Обратите внимание, что управление потоками требует аккуратности, так как неправильное использование потоков может привести к различным проблемам, таким как гонки данных и блокировки. Важно правильно синхронизировать доступ к общим ресурсам, к которым имеют доступ несколько потоков в вашем приложении.

Синхронизация между потоками и процессами

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

  1. Критические секции (Critical Sections):

    • Критические секции предоставляют простой и легковесный способ синхронизации между потоками внутри одного процесса.
    • Функции для работы с критическими секциями включают InitializeCriticalSection, EnterCriticalSection, LeaveCriticalSection, и DeleteCriticalSection.
  2. Мьютексы (Mutexes):

    • Мьютексы могут использоваться для синхронизации между разными процессами, а не только потоками внутри одного процесса.
    • Функции для работы с мьютексами включают CreateMutex, WaitForSingleObject, и ReleaseMutex.
  3. Семафоры (Semaphores):

    • Семафоры позволяют контролировать доступ к ресурсам, когда требуется счетчик.
    • Функции для работы с семафорами включают CreateSemaphore, WaitForSingleObject, и ReleaseSemaphore.
  4. События (Events):

    • События используются для уведомления одного или нескольких потоков или процессов о возникновении события.
    • Функции для работы с событиями включают CreateEvent, SetEvent, WaitForSingleObject, и другие.
  5. Критические ресурсы и мьютексы файла (File Mapping and File Mutexes):

    • Эти механизмы позволяют синхронизировать доступ к разделяемым данным, которые находятся в памяти или на диске.
    • Самые распространенные функции включают CreateFileMapping, MapViewOfFile, и WaitForSingleObject.
  6. Readers-Writers Locks:

    • Эти механизмы синхронизации позволяют определять правила доступа для читающих и записывающих потоков к общим данным.
    • Функции для работы с Reader-Writer Locks включают InitializeSRWLock, AcquireSRWLockExclusive, и AcquireSRWLockShared.
  7. События завершения (Completion Events):

    • Эти события используются для уведомления о завершении выполнения асинхронных операций ввода-вывода.
    • Функции для работы с событиями завершения включают CreateIoCompletionPort, GetQueuedCompletionStatus, и другие.

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

Завершение процессов

В Windows API (WinAPI) есть несколько способов завершения процессов, включая нормальное завершение и принудительное завершение. Вот основные функции и методы для завершения процессов:

  1. ExitProcess:

    • Функция ExitProcess используется для нормального завершения текущего процесса. Это завершает выполнение текущей программы и завершает процесс.

    Где exitCode — код завершения процесса, который будет возвращен операционной системой.

  2. TerminateProcess:

    • Функция TerminateProcess используется для принудительного завершения другого процесса. Это позволяет завершить процесс, даже если он не отвечает или заблокирован.
    BOOL success = TerminateProcess(hProcess, exitCode);

    Где hProcess — дескриптор процесса, который нужно завершить, и exitCode — код завершения процесса.

  3. WM_CLOSE и PostMessage:

    • Если вы хотите завершить приложение с графическим интерфейсом, вы можете отправить сообщение WM_CLOSE главному окну приложения с помощью функции PostMessage. Это позволит приложению выполнить закрытие как обычно.
    PostMessage(hWnd, WM_CLOSE, 0, 0);

    Где hWnd — дескриптор главного окна приложения.

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

Получение информации о процессах

Для получения информации о процессах в операционной системе Windows можно использовать Windows API. Существует несколько функций и структур данных, которые позволяют получить информацию о текущих работающих процессах. Вот пример получения списка процессов и их атрибутов:

#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <tchar.h>

int main() {
    SetConsoleOutputCP(1251);
    // Создаем объект, представляющий снимок всех процессов
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hProcessSnap == INVALID_HANDLE_VALUE) {
        // Обработка ошибки
        return 1;
    }

    // Структура, в которую будет сохранен атрибут процесса
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // Получаем информацию о первом процессе в снимке
    if (!Process32First(hProcessSnap, &pe32)) {
        CloseHandle(hProcessSnap);
        // Обработка ошибки
        return 1;
    }

    // Перебираем все процессы в снимке
    do {
        _tprintf(_T("Процесс ID: %d, Имя: %s\n"), pe32.th32ProcessID, pe32.szExeFile);
        // Здесь можно получать и обрабатывать другие атрибуты процесса

    } while (Process32Next(hProcessSnap, &pe32));

    CloseHandle(hProcessSnap);

    return 0;
}

Обратите внимание на следующие ключевые моменты в этом коде:

  1. CreateToolhelp32Snapshot создает снимок (snapshot) всех процессов в системе.

  2. Process32First используется для получения информации о первом процессе в снимке, а Process32Next для перебора всех остальных процессов.

  3. PROCESSENTRY32 — это структура, которая хранит атрибуты процесса, такие как идентификатор процесса (PID) и имя исполняемого файла.

  4. Информация о процессе выводится на экран с помощью _tprintf, но ее можно использовать для любой нужной обработки.

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

Модули процесса

Для получения информации о модулях (библиотеках и исполняемых файлах) внутри процесса в операционной системе Windows, вы можете использовать Windows API. Основным инструментом для этой задачи является функция EnumProcessModules. Вот пример использования:

#include <windows.h>
#include <psapi.h>
#include <tchar.h>
#include <stdio.h>

int main() {
    SetConsoleOutputCP(1251);
    DWORD processId = GetCurrentProcessId(); // Идентификатор текущего процесса
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);

    if (hProcess == NULL) {
        // Обработка ошибки открытия процесса
        return 1;
    }

    HMODULE hModules[1024];
    DWORD cbNeeded;

    // Получаем список модулей внутри процесса
    if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded)) {
        for (DWORD i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
            TCHAR szModName[MAX_PATH];

            // Получаем имя модуля
            if (GetModuleFileNameEx(hProcess, hModules[i], szModName, sizeof(szModName) / sizeof(TCHAR))) {
                _tprintf(_T("Модуль #%u: %s\n"), i, szModName);
            }
        }
    }

    CloseHandle(hProcess);
    return 0;
}

В этом примере мы:

  1. Получаем идентификатор текущего процесса с помощью GetCurrentProcessId.

  2. Открываем процесс с помощью OpenProcess, чтобы получить дескриптор процесса с правами на чтение информации о модулях.

  3. Используем EnumProcessModules для получения списка модулей внутри процесса. EnumProcessModules возвращает массив дескрипторов модулей.

  4. Для каждого модуля мы используем GetModuleFileNameEx, чтобы получить полный путь к модулю и выводим его имя.

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

Управление разрешениями и защитой процессов

Функции OpenProcessToken и GetTokenInformation в Windows API используются для получения информации о безопасности и разрешениях, связанных с процессом. Давайте рассмотрим их использование подробнее:

  1. OpenProcessToken:

    Функция OpenProcessToken используется для открытия дескриптора безопасности (токена) процесса. Этот токен содержит информацию о безопасности, такую как SID (идентификатор безопасности) пользователя и группы, разрешения и другие атрибуты безопасности процесса. Вот как можно использовать OpenProcessToken:

    #include <windows.h>
    #include <tchar.h>
    
    int main() {
        SetConsoleOutputCP(1251);
        DWORD processId = ...; // Идентификатор процесса, для которого нужно получить токен
        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processId);
    
        if (hProcess == NULL) {
            // Обработка ошибки
            return 1;
        }
    
        HANDLE hToken;
        if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
            // Теперь у вас есть дескриптор токена (hToken) для процесса,
            // который вы можете использовать для получения информации о безопасности.
    
            // Закрываем дескриптор токена после использования.
            CloseHandle(hToken);
        }
    
        CloseHandle(hProcess);
        return 0;
    }
  2. GetTokenInformation:

    После открытия дескриптора токена с помощью OpenProcessToken, вы можете использовать функцию GetTokenInformation, чтобы получить различные атрибуты и информацию о безопасности. Вы должны предоставить буфер для хранения информации. Вот пример получения информации о SID (идентификаторе безопасности) пользователя из токена:

    HANDLE hToken = ...; // Дескриптор токена
    DWORD dwSize = 0;
    
    // Получаем размер буфера, необходимый для информации о SID.
    GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
    
    // Выделяем буфер и получаем информацию о SID.
    PTOKEN_USER pTokenUser = (PTOKEN_USER)malloc(dwSize);
    if (GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize)) {
        // Теперь у вас есть информация о SID пользователя.
    
        // Освобождаем выделенный буфер после использования.
        free(pTokenUser);
    }
    
    // Закрываем дескриптор токена.
    CloseHandle(hToken);

Использование критических секций

Критические секции (Critical Sections) — это механизм синхронизации в Windows API, который позволяет защитить доступ к общим данным от одновременного доступа нескольких потоков внутри одного процесса. Они обычно используются для предотвращения гонок данных и обеспечения корректного доступа к разделяемым ресурсам. Вот пример использования критических секций:

#include <windows.h>

// Объявляем глобальную критическую секцию
CRITICAL_SECTION g_criticalSection;

// Функция, выполняемая в потоках
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
    // Входим в критическую секцию
    EnterCriticalSection(&g_criticalSection);

    // Здесь можно выполнять операции с общими данными

    // Выходим из критической секции
    LeaveCriticalSection(&g_criticalSection);

    return 0;
}

int main() {
    SetConsoleOutputCP(1251);
    // Инициализируем критическую секцию
    InitializeCriticalSection(&g_criticalSection);

    // Создаем потоки, которые будут использовать критическую секцию
    HANDLE hThread1 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
    HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);

    // Ожидаем завершения потоков
    WaitForSingleObject(hThread1, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);

    // Закрываем дескрипторы потоков
    CloseHandle(hThread1);
    CloseHandle(hThread2);

    // Уничтожаем критическую секцию
    DeleteCriticalSection(&g_criticalSection);

    return 0;
}

Обратите внимание на следующие моменты:

  1. Мы используем InitializeCriticalSection для инициализации критической секции и DeleteCriticalSection для ее удаления после использования.

  2. В функции ThreadFunction перед доступом к общим данным мы входим в критическую секцию с помощью EnterCriticalSection, а после завершения операций с общими данными мы выходим из нее с помощью LeaveCriticalSection.

  3. Потоки создаются с помощью CreateThread, и каждый из них выполняет ThreadFunction.

  4. Мы ожидаем завершения выполнения потоков с помощью WaitForSingleObject.

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

Использование мьютексов

Мьютексы (Mutexes) — это механизм синхронизации в Windows API, который используется для управления доступом к разделяемым ресурсам, чтобы предотвратить гонки данных между несколькими потоками или процессами. Мьютексы обычно используются для синхронизации между потоками в разных процессах. Вот пример использования мьютексов для синхронизации между двумя потоками:

#include <windows.h>
#include <stdio.h>

// Объявляем глобальный мьютекс
HANDLE g_mutex;

// Функция, выполняемая в потоках
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
    // Попытка захвата мьютекса
    DWORD dwWaitResult = WaitForSingleObject(g_mutex, INFINITE);

    if (dwWaitResult == WAIT_OBJECT_0) {
        // Мьютекс успешно захвачен

        // Здесь можно выполнять операции с разделяемыми ресурсами

        // Освобождение мьютекса
        ReleaseMutex(g_mutex);
    } else {
        // Обработка ошибки
    }

    return 0;
}

int main() {
    SetConsoleOutputCP(1251);
    // Создаем мьютекс
    g_mutex = CreateMutex(NULL, FALSE, NULL);

    if (g_mutex == NULL) {
        // Обработка ошибки создания мьютекса
        return 1;
    }

    // Создаем два потока
    HANDLE hThread1 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
    HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);

    // Ожидаем завершения потоков
    WaitForSingleObject(hThread1, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);

    // Закрываем дескрипторы потоков
    CloseHandle(hThread1);
    CloseHandle(hThread2);

    // Закрываем дескриптор мьютекса
    CloseHandle(g_mutex);

    return 0;
}

Обратите внимание на следующие моменты:

  1. Мы используем CreateMutex для создания мьютекса и CloseHandle для его закрытия после использования.

  2. В функции ThreadFunction мы используем WaitForSingleObject для попытки захвата мьютекса и ReleaseMutex для его освобождения после завершения операций с разделяемыми ресурсами.

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

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

Использование семафоров

Семафоры (Semaphores) — это ещё один механизм синхронизации в Windows API, который используется для контроля доступа к разделяемым ресурсам между несколькими потоками или процессами. Семафоры могут позволить нескольким потокам одновременно получить доступ к общему ресурсу в ограниченном количестве. Вот пример использования семафора для синхронизации между несколькими потоками:

#include <windows.h>
#include <stdio.h>

// Объявляем глобальный семафор и устанавливаем начальное значение
HANDLE g_semaphore;

// Функция, выполняемая в потоках
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
    // Попытка уменьшить счетчик семафора
    DWORD dwWaitResult = WaitForSingleObject(g_semaphore, INFINITE);

    if (dwWaitResult == WAIT_OBJECT_0) {
        // Семафор успешно уменьшен

        // Здесь можно выполнять операции с разделяемыми ресурсами

        // Увеличение счетчика семафора
        ReleaseSemaphore(g_semaphore, 1, NULL);
    } else {
        // Обработка ошибки
    }

    return 0;
}

int main() {
    SetConsoleOutputCP(1251);
    // Создаем семафор с начальным счетчиком
    g_semaphore = CreateSemaphore(NULL, 2, 2, NULL); // В данном примере, начальный счетчик равен 2

    if (g_semaphore == NULL) {
        // Обработка ошибки создания семафора
        return 1;
    }

    // Создаем два потока
    HANDLE hThread1 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
    HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);

    // Ожидаем завершения потоков
    WaitForSingleObject(hThread1, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);

    // Закрываем дескрипторы потоков
    CloseHandle(hThread1);
    CloseHandle(hThread2);

    // Закрываем дескриптор семафора
    CloseHandle(g_semaphore);

    return 0;
}

Обратите внимание на следующие моменты:

  1. Мы используем CreateSemaphore для создания семафора и CloseHandle для его закрытия после использования. Начальное значение счетчика семафора в данном примере равно 2, что позволяет двум потокам одновременно получить доступ к разделяемым ресурсам.

  2. В функции ThreadFunction мы используем WaitForSingleObject для попытки уменьшения счетчика семафора и ReleaseSemaphore для его увеличения после завершения операций с разделяемыми ресурсами.

  3. Семафор может позволить одновременно получить доступ только указанному количеству потоков (в данном случае, двум потокам). Если больше потоков попытаются одновременно уменьшить счетчик семафора, они будут блокированы до его освобождения другими потоками.

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

Использование событий

События (Events) — это механизм синхронизации в Windows API, который используется для уведомления одного или нескольких потоков о наступлении определенного события. События могут быть использованы для синхронизации между потоками или процессами, когда один поток ждет, пока другой поток или процесс оповестит его о наступлении события. Вот пример использования событий:

#include <windows.h>
#include <stdio.h>

// Объявляем глобальное событие
HANDLE g_event;

// Функция, выполняемая в потоках
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
    // Ожидание события
    DWORD dwWaitResult = WaitForSingleObject(g_event, INFINITE);

    if (dwWaitResult == WAIT_OBJECT_0) {
        // Событие успешно сработало

        // Здесь можно выполнять действия, связанные с событием

        printf("Событие сработало в потоке.\n");
    } else {
        // Обработка ошибки
    }

    return 0;
}

int main() {
    SetConsoleOutputCP(1251);
    // Создаем событие
    g_event = CreateEvent(NULL, FALSE, FALSE, NULL);

    if (g_event == NULL) {
        // Обработка ошибки создания события
        return 1;
    }

    // Создаем поток
    HANDLE hThread = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);

    // Задержка для демонстрации события
    Sleep(2000);

    // Устанавливаем событие
    SetEvent(g_event);

    // Ожидаем завершения потока
    WaitForSingleObject(hThread, INFINITE);

    // Закрываем дескриптор потока
    CloseHandle(hThread);

    // Закрываем дескриптор события
    CloseHandle(g_event);

    return 0;
}

Обратите внимание на следующие моменты:

  1. Мы используем CreateEvent для создания события и CloseHandle для его закрытия после использования. В данном примере событие создается в несигнальном состоянии (сигнал равен FALSE), что означает, что ожидающие потоки или процессы будут блокированы до тех пор, пока событие не будет установлено.

  2. В функции ThreadFunction мы используем WaitForSingleObject для ожидания события. Когда событие сработает (будет установлено), поток продолжит выполнение и выполнит действия, связанные с событием.

  3. В основной функции, после создания потока, мы устанавливаем событие с помощью SetEvent. Это приводит к тому, что ожидающий поток ThreadFunction сразу же продолжает выполнение.

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

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Easy context menu для windows 11
  • Extreme injector v3 не запускается windows 10
  • Почему герои 6 не запускаются на windows 10
  • Start process under windows
  • Команда удалить каталог windows