Реферат - Потоки / Потоки и процессы
.docconstructor Create(const Name: string);
задает имя создаваемого объекта. Первоначально он не принадлежит никому. (Но функция API createMutex, вызываемая в нем, позволяет передать созданный объект тому потоку, в котором это произошло.) Далее метод:
function Get(TimeOut: Integer): Boolean;
производит попытку в течение Timeout миллисекунд завладеть объектом (в этом случае результат равен True). Если объект более не нужен, следует вызвать метод:
function Release: Boolean;
Программист может использовать взаимное исключение, чтобы избежать считывания и записи общей памяти несколькими потоками одновременно.
Критическая секция. Работая в Delphi, программист может также использовать объект типа критическая секция (critical section). Критические секции подобны взаимным исключениям по сути, однако между ними существуют два главных отличия. 1Взаимные исключения могут быть совместно использованы потоками в различных процессах. 2 Если критическая секция принадлежит другому потоку, ожидающий поток блокируется вплоть до освобождения критической секции. В отличие от этого, взаимное исключение разрешает продолжение по истечении тайм-аута. Критические секции и взаимные исключения очень схожи. На первый взгляд, выигрыш от использования критической секции вместо взаимного исключения не очевиден. Критические секции, однако, более эффективны, чем взаимные исключения, так как используют меньше системных ресурсов. Взаимные исключения могут быть установлены на определенный интервал времени, по истечении которого выполнение продолжается; критическая секция всегда ждет столько, сколько потребуется.
Возьмем класс TCriticalSection (модуль syncobjs.pas). Логика использования его проста — "держать и не пущать". В многопотоковом приложении создается и инициализируется общая для всех потоков критическая секция. Когда один из потоков достигает критически важного участка кода, он пытается захватить секцию вызовом метода Enter:
MySection.Enter;
try DoSomethingCritical;
finally MySection.Leave;
end;
Когда другие потоки доходят до оператора захвата секции и обнаруживают, что она уже захвачена, они приостанавливаются вплоть до освобождения секции первым потоком путем вызова метода Leave. Обратите внимание, что вызов Leave помещен в конструкцию try.. finally — здесь требуется повышенная надежность. Критические секции являются системными объектами и подлежат обязательному освобождению — впрочем, как и остальные рассматриваемые здесь объекты.
Семафор. Семафор (semaphore) подобен взаимному исключению. Разница между ними в том, что семафор может управлять количеством потоков, которые имеют к нему доступ. Семафор устанавливается на предельное число потоков, которым доступ разрешен. Когда это число достигнуто, последующие потоки будут приостановлены, пока один или более потоков не отсоединятся от семафора и не освободят доступ.
В качестве примера использования семафора рассмотрим случай, когда каждый из группы потоков работает с фрагментом совместно используемого пула памяти. Так как совместно используемая память допускает обращение к ней только определенного числа потоков, все прочие должны быть блокированы вплоть до момента, когда один или несколько пользователей пула откажутся от его совместного использования.
Процесс. Порождение дочернего процесса. Объект типа процесс (process) может быть использован для того, чтобы приостановить выполнение потока в том случае, если он для своего продолжения нуждается в завершении процесса. С практической точки зрения такая проблема встает, когда нужно в рамках вашего приложения исполнить приложение, созданное кем-то другим, или, к примеру, сеанс MS DOS.
Рассмотрим, как, собственно, один процесс может породить другой. Вместо устаревшей и поддерживаемой только для совместимости функции winExec, перекочевавшей из прежних версий Windows, гораздо правильнее использовать более мощную:
function CreateProcess(IpApplicationName: PChar; IpCommandLine: PChar;
IpProcessAttributes, IpThreadAttributes: PSecurityAttributes;
bInheritHandles: BOOL; dwCreationFlags: DWORD; IpEnvironment: Pointer;
IpCurrentDirectory: PChar; const IpStartupInfo: TStartupInfo;
var lpProce53lnfomation: TProcessInformation): BOOL;
Первые два параметра ясны — это имя запускаемого приложения и передаваемые ему в командной строке параметры. Параметр dwCreationFiags содержит флаги, определяющие способ создания нового процесса и его будущий приоритет. Использованные в приведенном ниже примере флаги означают: create_new_console — будет запущено новое консольное приложение с отдельным окном; NORMAL_PRIORITY_CLASS — нормальный приоритет. Структура TStartupinfo содержит сведения о размере, цвете, положении окна создаваемого приложения. В нижеследующем примере используется поле wShowWindow: установлен флаг sw_shownormal, означающий визуализацию окна с нормальным размером. На выходе функции заполняется структура ipprocessinformation. В ней программисту возвращаются дескрипторы и идентификаторы созданного процесса и его первичного потока. Нам понадобится дескриптор процесса — в нашем примере создается консольное приложение, и затем происходит ожидание его завершения. "Просигналит" нам об этом именно объект Ipprocessinformation.hProcess.
var
IpStartupInfo: TStartupinfo;
IpProceasInformation: TProcessInformation;
begin FillChar(IpStartupInfo,Sizeof(IpStartupInfo),#0);
IpStartupInfo.cb := Sizeof(IpStartupInfo);
IpStartupInfo.dwFlags := STARTFJJSESHOWWINDOW;
IpStartupInfo.wShowWindow := sw_shownormal;
if not CreateProcess(nil,PCharfarj /?'), nil, nil, false,CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil,
IpStartupInfo, Ipprocessinformation) then
ShowMessage(IntToStr(GetLastError)) else
begin WaitForSingleObject(IpProcessInfomation
.hProcess,10000) ;
CloseHandle(IpProcessInformation.hProcess) ;
end;
end;
Поток. Поток может ожидать другой поток, точно так же, как и другой процесс. Ожидание можно организовать с помощью функций API (как в только что рассмотренном примере), но удобнее это сделать при помощи метода TThread.Wait For.
Консольный ввод. Консольный ввод (console input) годится для потоков, которые должны ожидать отклика на нажатие пользователем клавиши на клавиатуре. Этот тип ожидания может быть использован в программе дуплексной связи (вроде Chat из состава Windows). Один поток при этом будет ожидать получения символов; второй — отслеживать ввод пользователя и затем отсылать набранный текст ожидающему приложению.
Оповещение об изменении в файловой системе. Этот вид объекта ожидания очень интересен и незаслуженно мало известен. Мы рассмотрели практически все варианты того, как один поток может подать сигнал другому. А как получить сигнал от операционной системы? Ну, например, о том, что в файловой системе произошли какие-то изменения? Такой вид оповещения позаимствован из ОС Unix и теперь доступен программистам, работающим с Win 32.
Для организации мониторинга файловой системы нужно использовать три функции — FindFirstChangeNotification, FindNextChangeNotification И FindcioseChangeNotification. Первая из них возвращает дескриптор объекта файлового оповещения, который можно передать в функцию ожидания. Объект активизируется тогда, когда в заданной папке произошли те или иные изменения (создание или уничтожение файла или папки, изменение прав доступа и т. д.). Вторая — готовит объект к реакции на следующее изменение. Наконец, с помощью третьей функции следует закрыть ставший ненужным объект.
Так может выглядеть код метода Execute потока, созданного для мониторинга:
var DirName : string;
procedure TSimpleThread.Execute;
var r: Cardinal;
fn : THandle;
begin
fn := FindFirstChangeNotification(pChar(DirName),
True,FILE_NOTIFY_CHANGE_FILE_NAME) ;
repeat r := WaitForSingleObject(fn,2000);
if r = WAIT_OBJECT_0 then Forml.UpdateList;
if not FindNextChangeNotification (fn) then break;
until Terminated;
FindCloseChangeNotification(fn) ;
end;
На главной форме должны находиться компоненты, нужные для выбора обследуемой папки, а также компонент TListBox, в который будут записываться имена файлов:
procedure TFormI.ButtonlClick(Sender:
TObject);
var dir : string;
begin
if SelectDirectory(dir,[],0) then begin Editl.Text := dir;
DirName := dir;
end;
end;
procedure TFormI.UpdateList;
var SearchRec: TSearchRec;
begin
ListBoxl.Clear;
FindFirst(Editl.Text+'\*.*', faAnyFile, SearchRec);
repeat ListBoxl.Items.Add(SearchRec.Name) ;
until FindNext(SearchRec) <> 0;
FindClose(SearchRec) ;
end;
Приложение готово. Чтобы оно стало полнофункциональным, предусмотрите в нем механизм перезапуска потока при изменении обследуемой папки.
Локальные данные потока. Интересная проблема возникает, если в приложении будет несколько одинаковых потоков. Как избежать совместного использования одних и тех же переменных несколькими потоками? Первое, что приходит на ум — добавить и использовать поля — потомка TThread, которые можно добавить при его создании. Каждый поток соответствует отдельному экземпляру объекта, и их данные пересекаться не будут. (Кстати, это одна из целей, ради которых разрабатывался TThread.) Но есть функции API, которые знать не знают об объектах Delphi и их полях и свойствах. Для поддержки разделения данных между потоками на нижнем уровне в язык Object Pascal введена специальная директива — threadvar, которая отличается от директивы описания переменных var тем, что применяется только к локальным данным потока. Следующее описание: Var
datal: Integer;
threadvar
data2: Integer;
означает, что переменная datal будет использоваться всеми потоками данного приложения, а переменная data2 будет у каждого потока своя. Как избежать одновременного запуска двух копий одного приложения. Такая задача возникает очень часто. Многие, особенно начинающие, пользователи не вполне понимают, что между щелчком по значку приложения и его запуском может пройти несколько секунд, а то и десятков секунд. Они начинают щелкать по значку, запуская все новые копии. Между тем, при работе с базами данных и во многих других случаях иметь более одной копии не только не нужно, но и вредно. Идея заключается в том, чтобы первая создаваемая копия приложения захватывала некий ресурс, а все последующие при запуске пытались сделать то же самое и в случае неудачи завершались. Пример такого ресурса — общий блок в файле, отображаемом в память. Поскольку этот ресурс имеет имя, можно сделать его уникальным именно для вашего приложения: var UniqueMapping : THandle;
begin
UniqueMapping := CreateFileMapping($ffffffff,
nil, PAGE_READONLY, 0, 32,'MyMap');
if UniqueMapping - 0 then
begin
ShowMessage('Ошибка выделения памяти!');
Halt;
end
else if GetLastError = ERROR_ALREADY_EXISTS then
begin
ShowMessage('Вторую копию задачи запускать нельзя!');
Halt;
end;
//можно продолжать
Application.Initialize;
Примерно такие строки нужно вставить в начало текста проекта, до создания форм. Блок совместно используемой памяти выделяется в системном страничном файле (об этом говорит первый параметр, равный -1, см. описание функции CreateFiieMapping). его имя — МуМар. если при создании блока будет получен код ошибки error_already exists, это свидетельствует о наличии работающей копии приложения. В этом случае приложение завершается; в противном случае процесс инициализации продолжается.
