Реализация многопоточности в delphi.Вижу множество вопросов на разных форумах по программированию которые относятся к многопоточности. В этой статье я бы хотел не только объяснить как реализовать это в delphi, но и предоставить готовый шаблон многопоточного приложения который вы бы могли использовать в дальнейшем в своих программах. Для начала немного теории. Одной из важнейших вещей в многопоточном приложении является синхронизация потоков. Если не учесть, синхронизацию потоков, то это, может привести к плачевным последствиям. Давайте рассмотрим такой пример, после чего у вас в голове всё встанет на свои места. У нас в программе имеется переменная, назовём её - "int" - типа integer. Если мы будем присваивать ей значение из основного потока, то всё будет хорошо, и проблем не возникнет. Мало того, если даже мы присвоим ей значение из дополнительного потока который мы создали, то всё будет хорошо. Но так как зачастую у многопоточного приложения более одного дополнительного потока, то зададимся вопросом- "А, что будет если мы попытаемся присвоить переменной значение из двух потоков одновременно?". Вот тут, и произойдёт то таинственное события при котором переменной присвоится значение которое скорее всего не будет равно ни одному из значений которых вы планировали ей присвоить. Тогда и вступает в игру метод синхронизации synchronize. С помощь данного нам разработчиками delphi метода, вы можете обеспечить безопасное обращение нескольких потоков к одной переменной. Данный метод обеспечивает следующие: при вызове процедуры с помощью synchronize она начинает выполнятся, а все остальные процедуры вызванные с помощью этого метода встают в очередь и ждут выполнения текущей процедуры. Всё вышеописанное относится только к записи переменных, а читать можно одновременно из нескольких потоков и ничего плохого не произойдёт. Ну а теперь перейдём к практике. И так создадим новый проект. И начнём писать поток. После вот этих строк: Code: private { Private declarations } public { Public declarations } end; Добавим свои: Code: potok = class(TThread) //Этой строкой мы унаследовали класс потока private str: string;//в разделе private описываются переменные с помощью которых мы nomer : Integer;//будем передавать значения между процедурами внутри потока protected procedure Execute; override;//это главная процедура потока, она начинает свою работу //после того как мы создали поток public procedure synchro;//в разделе public вы можете объявить процедуры какие только душе //угодно constructor Create(CreateSuspended: Boolean);//эта строка говорит о том, что мы в //implementation опишем конструкцию //потока end; Ну здесь вроде бы всё понятно, вы можете добавлять сколько угодно переменных и процедур для удобства работы. Под implementation нам надо описать конструкцию потока, в данном примере это не обязательно, но, что бы вы знали на будущие мы всё равно это сделаем. Code: constructor potok.Create(CreateSuspended: Boolean); begin inherited Create(CreateSuspended);//Эта строка говорит о том, что поток после создания //будет приостановлен если ему передать значение true при создание, если false, то сразу //начнёт работу. end; Добавим в глобальные переменные переменную - "nom" - типа integer, а так же массив наших потоков. Code: var Form1: TForm1; nom:integer; a: array [1..10] of potok;//массив для хранения наших потоков Теперь нужно описать процедуры потока, под implementation добавляем следующие: Code: procedure potok.Execute;//начинаем описывать главную процедуру потока var i:integer; begin for i:=0 to 100 do begin sleep(1000); synchronize(synchro);//этой строкой мы вызываем процедуру synchro в единичном экземпляре end; end; procedure potok.synchro; //описываем ещё одну процедуру потока, которая будет менять //загаловок form1 begin inc(nom); form1.Caption:='Поток делает своё дело - '+inttostr(nom); end; Собственно всё готово, остаются только запустить потоки. Кидаем кнопку на форму, "кастуем на неё даблклик" и пишем: Code: procedure TForm1.Button1Click(Sender: TObject); var pot:integer; begin for pot:=1 to 10 do //цикл запускает 10 потоков, которые будут изменять заголовок a[pot]:=potok.Create(false); //формы, так же идёт //добавление в массив, что бы потом вы могли уничтожить //один поток. end; Вот так выглядит программа в итоге: Code: unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; potok = class(TThread) //Этой строкой мы унаследовали класс потока private str: string;//в разделе private описываются переменные с помощью которых мы nomer : Integer;//будем передавать значения между процедурами внутри потока protected procedure Execute; override;//это главная процедура потока, она начинает свою работу //после того как мы создали поток public procedure synchro;//в разделе public вы можете объявить процедуры какие только душе //угодно constructor Create(CreateSuspended: Boolean);//эта строка говорит о том, что мы в //implementation опишем конструкцию //потока end; var a: array [1..10] of potok; Form1: TForm1; nom:integer; implementation constructor potok.Create(CreateSuspended: Boolean); begin inherited Create(CreateSuspended);//Эта строка говорит о том, что поток после создания //будет приостановлен если ему передать значение true при создание, если false, то сразу //начнёт работу. end; {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var pot:integer; begin for pot:=1 to 10 do //цикл запускает 10 потоков, которые будут изменять заголовок a[pot]:=potok.Create(false); //формы, так же идёт добавление в массив, что бы потом вы могли их уничтожить по одному. end; procedure potok.Execute;//начинаем описывать главную процедуру потока var I:integer; begin for i:=0 to 100 do begin sleep(1000); synchronize(synchro);//этой строкой мы вызываем процедуру synchro в единичном экземпляре end; end; procedure potok.synchro; //описываем ещё одну процедуру потока, которая будет менять //загаловок form1 begin inc(nom); form1.Caption:='Поток делает своё дело - '+inttostr(nom); end; end. Запускаем программу, и нажимаем на кнопку. И видим как в заголовке формы по очереди увеличивается число. ЧТД как говорится. Удачи в создание приложений. С уважением Tip.the.besT. Автор: Tip.the.besT
Так запускать поток Не, ок а если я захочу конкретный экземпляр потока прибить и. т. д. как мне до него добраца. Synhronize гдето писали что лучше его не использовать для работы с глоб. переменными а только для синхронизации работы с формой. Описывать класс потока в одном юните с формой не ок. Шаблон этот создаетcя File->New->Other->ThreadObject Коментировать так детально код, не ок, все черты мешаються с кодом.
Ну с тем, что я не добавил потоки в массив я согласен, статью дополнил. Не знаю где писали про Synhronize, но если ты запустишь мой пример, то убедишься, что нормально всё с переменными. Интересно и как же описания потока в одном юните повлияет на работу программы? Конечно, тебе комментарии не нужны. А человеку придётся возвращаться выше и перечитывать, что бы понять. Итого. Достаточно было написать: "Добавь потоки в массив."
НУ я имел введу коментари, Достаточно прокомментировать в объявлении класса. Подобный комментарий меня вводит в ступор, человека знающего азы делфи, что же будет с новичком? Зачем переопределять конструктор?? Если в нем нечего не происходит? А в private секции нельзя? А нужно в приват, это правило хорошего тона что не юзаеться напрямую извне совать в привате. И одна из таких кому то мб и не покажеться проблемой. Когда мы объявляем данные в Private секции они доступны классам и т д, объявленным в том же юните, соответственно безопасность работы с классом нарушается, ну и автоподстройка будет выдавать приват члены класса, а на сонную голову из-за этого можно потом долго ошибку ловить. Ну и одно из важнейших замечаний при работе с потоками ты не указал надо обрабатывать все Except-шены, иначе поток навернется и память потечет рекой.... Label тоже его может вызвать.,. Массив для работы с потоками тоже не лучший вариант я об об этом узнал правда только сегодня днем.. TObjectList ок вариант, точнее наследник от него. Длинные/много строчные комментарии лучше писать так. {Комент.... Комент,..} Ну и ты не написал о том что это абстрактный класс и от него всегда надо наследоваца. Об этом я написал выше, но в сокращенном виде звучит так, классы из того юнита в котором объявлен твой поток будут иметь доступ к Private членам класса. А память то не освобождаеца.... Это самая большая ошибка, неофиты врятли ее увидят. И будут думать после нных запусков куда делась память???
уверен? "Директива Private начинает раздел данных (полей) и подпрограмм (методы) класса, которые являются частными (внутренними) для этого класса."
Если бы ты читал не поверхностно, то возможно заметил бы следующие: Каждый решает для себя как оформлять код, и честно говоря я бы лучше послушал комментарии тех людей которые разбираются с потоками по этой статье, и они бы сказали, что им конкретно не понятно. В размещении кода в одном юните я не вижу ничего страшного. А вот утечку памяти я не учёл, так как я уделил внимание именно потокам и организации их создания. Про массив тоже слышу первый раз. И пока не сталкивался с моментами в которых он работает нестабильно. Исключения я с удовольствием добавлю. Так же считаю, что пора завязывать с этим спором, так как каждый будет стоять на своём.
Немного ошибся из других классов доступ не получить(напрямую к полю), а вот у создананого экземпляра можно. Code: SimpleClass = class private G: string; end; Code: var Form1: TForm1; H : SimpleClass; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin H.G := 'g'; end; Тесть получаем доступ к приват члену в обход свойств(если бы были). Если все это объявлено в одном юните.
Не проще особенно когда, ценишь время, тут в два клика создается класс почти готовый к работе, в который можно передавать данные без напрягов, не через глобальные переменные. С BeginThread для прямой передачи данных надо описывать свой тип передавать в него данные... Я пользовался сначала BeginThread когда попробовал TThread(научился с ним работать), до этого я не вьежал как переопределить метод и многово не понимал... Я потом окуел сколько я тратил времени на создание потоков через BeginThread...(В плане велосипедов для передачи данных в поток ) Сча вот пишу компонент чтоб кинул на форму, указатели на форму и нужные классы дал и готово.. И самое главное использование TThread это ООП стиль программирования(если вся программа в нем написана), а BeginThread\CreatThread Функциональный\процедурный(я различие обоих стилей не знаю(функц.проц.)). Ну и это опять же увеличение повторного использования кода. Вместо BeginThread(Параметры); Я зделаю, Thread := TThread.Create(замороженный\не замороженный); Конечно можно выполнить создание через макросы, но как по мне целесообразней юзать TThread для десктоп софта, не для ботов и.т.д.
По мне гораздо легче использовать BeginThread/EndThreadEnterCrititcalSection/LeaveCriticalSection Code: procedure ProcedureForAllThreads(); begin EnterCriticalSection(CS); //Тут располагаем код, который должен выполняться только в одном потоке LeaveCriticalSection(CS); end; thread:=BeginThread(nil,1024,@ProcedureForAllThreads,nil,0,ThreadIdVarCardinalType); TerminateThread(thread,0);
Чем они тяжелы тем что не заставляют каждый раз одно и тоже писать?? Ну и когда захотеться оптимизировать свою работы, создать класс для работы с потоками под. специфические задачи легче унаследоваца от TThread чем пелить велосипед и отлаживать его. TThread это прежде всего абстракция над апи потоками дабы программист думал как реализовать функции которые будут в потоке а не как их запилить. Ну и разработка с TThread быстрее осуществляются и по качеству апи потокам не уступает. К томуже кучу классов готовых придумали дабы сэкономить наше время. А где CloseHandle(thread)? Уу все утечка хэндлов.... На мдсне не нашел строчек о том что TerminateThread хэндл закрывает.. Code: procedure ProcedureForAllThreads(); begin EnterCriticalSection(CS); //Тут располагаем код, который должен выполняться только в одном потоке LeaveCriticalSection(CS); end; А убивать через TerminateThread нужно только в крайних случаях, не советуйте плохого) Лучше через ExitThread();
А чего именно плохого в закрытии потока через TerminateThread? (c)mdsn И чем вообще плохо на апи потоки юзать, нету исключений, проверять рез. выполнения каждой функции ка кто напряжено.
"Поток можно завершить принудительно, вызвав ExitThread. При этом освобождаются все ресурсы операционной системы, выделенные дан ному потоку, но C/C++ - pеcypcы (например, объекты, созданные из С++-классов) не очищаются. Именно поэтому лучше возвращать управление из функции потока, чем самому вызывать функцию ExitThread." (Д. Рихтер)
Чет я не вкурил. Но у меня обычно на BeginThread код так. Code: procedure; begin ExitThread(0); end; В каком понятии очистить если я принудительно вызываю перед завершением потока деструкторы?
Нового не нашел ничего, при чтении/изучении кода вероятность запутаться больше, нежели что-то понять, особенно для новичков. Был бы благодарен, за четкую, статейку с подробным описание и примерами по синхронизации потоков. За старания +.
работает , даже в memo записывает , Spoiler: 1 unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; potok = class(TThread) //Этой строкой мы унаследовали класс потока private str: string;//в разделе private описываются переменные с помощью которых мы nomer : Integer;//будем передавать значения между процедурами внутри потока protected procedure Execute; override;//это главная процедура потока, она начинает свою работу //после того как мы создали поток public procedure synchro;//в разделе public вы можете объявить процедуры какие только душе //угодно constructor Create(CreateSuspended: Boolean);//эта строка говорит о том, что мы в //implementation опишем конструкцию //потока end; var a: array [1..10] of potok; Form1: TForm1; nom:integer; implementation constructor potok.Create(CreateSuspended: Boolean); begin inherited Create(CreateSuspended);//Эта строка говорит о том, что поток после создания //будет приостановлен если ему передать значение true при создание, если false, то сразу //начнёт работу. end; {$R *.dfm} procedure Ping(IP: String; OutMemo:TMemo); const BUFSIZE = 2000; var SecAttr : TSecurityAttributes; hReadPipe, hWritePipe : THandle; StartupInfo: TStartUpInfo; ProcessInfo: TProcessInformation; Buffer : Pchar; WaitReason, BytesRead : DWord; begin with SecAttr do begin nlength := SizeOf(TSecurityAttributes); binherithandle := true; lpsecuritydescriptor := nil; end; if Createpipe (hReadPipe, hWritePipe, @SecAttr, 0) then begin Buffer := AllocMem(BUFSIZE + 1); FillChar(StartupInfo, Sizeof(StartupInfo), #0); StartupInfo.cb := SizeOf(StartupInfo); StartupInfo.hStdOutput := hWritePipe; StartupInfo.hStdInput := hReadPipe; StartupInfo.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW; StartupInfo.wShowWindow := SW_HIDE; if CreateProcess(nil, PChar('ping.exe '+IP), @SecAttr, @SecAttr, true, NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo) then begin repeat WaitReason := WaitForSingleObject( ProcessInfo.hProcess,100); Application.ProcessMessages; until (WaitReason <> WAIT_TIMEOUT); Repeat BytesRead := 0; ReadFile(hReadPipe, Buffer[0], BUFSIZE, BytesRead, nil); Buffer[BytesRead]:= #0; OemToAnsi(Buffer,Buffer); OutMemo.Text := OutMemo.text + String(Buffer); until (BytesRead < BUFSIZE); end; FreeMem(Buffer); CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); CloseHandle(hReadPipe); CloseHandle(hWritePipe); end; end; procedure TForm1.Button1Click(Sender: TObject); var pot:integer; begin for pot:=1 to 10 do //цикл запускает 10 потоков, которые будут изменять заголовок a[pot]:=potok.Create(false); //формы, так же идёт добавление в массив, что бы потом вы могли их уничтожить по одному. Ping('127.0.0.1', Memo1); end; procedure potok.Execute;//начинаем описывать главную процедуру потока var I:integer; begin for i:=0 to 100 do begin sleep(1000); synchronize(synchro);//этой строкой мы вызываем процедуру synchro в единичном экземпляре end; end; procedure potok.synchro; //описываем ещё одну процедуру потока, которая будет менять //загаловок form1 begin inc(nom); //form1.Caption:=' '+inttostr(nom); Form1.Memo1.Lines.Add(inttostr(nom)); end; end.