![]() |
Реализация многопоточности в delphi. Вижу множество вопросов на разных форумах по программированию которые относятся к многопоточности. В этой статье я бы хотел не только объяснить как реализовать это в delphi, но и предоставить готовый шаблон многопоточного приложения который вы бы могли использовать в дальнейшем в своих программах.Для начала немного теории. Одной из важнейших вещей в многопоточном приложении является синхронизация потоков. Если не учесть, синхронизацию потоков, то это, может привести к плачевным последствиям. Давайте рассмотрим такой пример, после чего у вас в голове всё встанет на свои места. У нас в программе имеется переменная, назовём её - "int" - типа integer. Если мы будем присваивать ей значение из основного потока, то всё будет хорошо, и проблем не возникнет. Мало того, если даже мы присвоим ей значение из дополнительного потока который мы создали, то всё будет хорошо. Но так как зачастую у многопоточного приложения более одного дополнительного потока, то зададимся вопросом- "А, что будет если мы попытаемся присвоить переменной значение из двух потоков одновременно?". Вот тут, и произойдёт то таинственное события при котором переменной присвоится значение которое скорее всего не будет равно ни одному из значений которых вы планировали ей присвоить. Тогда и вступает в игру метод синхронизации synchronize. С помощь данного нам разработчиками delphi метода, вы можете обеспечить безопасное обращение нескольких потоков к одной переменной. Данный метод обеспечивает следующие: при вызове процедуры с помощью synchronize она начинает выполнятся, а все остальные процедуры вызванные с помощью этого метода встают в очередь и ждут выполнения текущей процедуры. Всё вышеописанное относится только к записи переменных, а читать можно одновременно из нескольких потоков и ничего плохого не произойдёт. Ну а теперь перейдём к практике. И так создадим новый проект. И начнём писать поток. После вот этих строк: Код:
privateКод:
potok = class(TThread) //Этой строкой мы унаследовали класс потокаПод implementation нам надо описать конструкцию потока, в данном примере это не обязательно, но, что бы вы знали на будущие мы всё равно это сделаем. Код:
constructor potok.Create(CreateSuspended: Boolean);Код:
varКод:
procedure potok.Execute;//начинаем описывать главную процедуру потокаКод:
procedure TForm1.Button1Click(Sender: TObject);Код:
unit Unit1;С уважением Tip.the.besT. Автор: Tip.the.besT |
Так запускать поток
Цитата:
Synhronize гдето писали что лучше его не использовать для работы с глоб. переменными а только для синхронизации работы с формой. Описывать класс потока в одном юните с формой не ок. Шаблон этот создаетcя File->New->Other->ThreadObject Коментировать так детально код, не ок, все черты мешаються с кодом. Цитата:
|
Цитата:
Не знаю где писали про Synhronize, но если ты запустишь мой пример, то убедишься, что нормально всё с переменными. Интересно и как же описания потока в одном юните повлияет на работу программы? Конечно, тебе комментарии не нужны. А человеку придётся возвращаться выше и перечитывать, что бы понять. Итого. Достаточно было написать: "Добавь потоки в массив." |
Цитата:
|
Цитата:
Цитата:
Цитата:
Зачем переопределять конструктор?? Если в нем нечего не происходит? Цитата:
А нужно в приват, это правило хорошего тона что не юзаеться напрямую извне совать в привате. И одна из таких кому то мб и не покажеться проблемой. Когда мы объявляем данные в Private секции они доступны классам и т д, объявленным в том же юните, соответственно безопасность работы с классом нарушается, ну и автоподстройка будет выдавать приват члены класса, а на сонную голову из-за этого можно потом долго ошибку ловить. Ну и одно из важнейших замечаний при работе с потоками ты не указал надо обрабатывать все Except-шены, иначе поток навернется и память потечет рекой.... Label тоже его может вызвать.,. Массив для работы с потоками тоже не лучший вариант я об об этом узнал правда только сегодня днем.. TObjectList ок вариант, точнее наследник от него. Длинные/много строчные комментарии лучше писать так. {Комент.... Комент,..} Ну и ты не написал о том что это абстрактный класс и от него всегда надо наследоваца. Цитата:
А память то не освобождаеца.... Это самая большая ошибка, неофиты врятли ее увидят. И будут думать после нных запусков куда делась память??? |
Цитата:
"Директива Private начинает раздел данных (полей) и подпрограмм (методы) класса, которые являются частными (внутренними) для этого класса." |
Цитата:
Цитата:
В размещении кода в одном юните я не вижу ничего страшного. А вот утечку памяти я не учёл, так как я уделил внимание именно потокам и организации их создания. Про массив тоже слышу первый раз. И пока не сталкивался с моментами в которых он работает нестабильно. Исключения я с удовольствием добавлю. Так же считаю, что пора завязывать с этим спором, так как каждый будет стоять на своём. |
Цитата:
Код:
SimpleClass = classКод:
varЕсли все это объявлено в одном юните. |
а не проще & лучше вместо TThread юзать апи потоки? (beginthread - createthread)
|
Цитата:
С BeginThread для прямой передачи данных надо описывать свой тип передавать в него данные... Я пользовался сначала BeginThread когда попробовал TThread(научился с ним работать), до этого я не вьежал как переопределить метод и многово не понимал... Я потом окуел сколько я тратил времени на создание потоков через BeginThread...(В плане велосипедов для передачи данных в поток ) Сча вот пишу компонент чтоб кинул на форму, указатели на форму и нужные классы дал и готово.. И самое главное использование TThread это ООП стиль программирования(если вся программа в нем написана), а BeginThread\CreatThread Функциональный\процедурны (я различие обоих стилей не знаю(функц.проц.)). Ну и это опять же увеличение повторного использования кода. Вместо BeginThread(Параметры); Я зделаю, Thread := TThread.Create(замороженный\не замороженный); Конечно можно выполнить создание через макросы, но как по мне целесообразней юзать TThread для десктоп софта, не для ботов и.т.д. |
Цитата:
|
По мне гораздо легче использовать BeginThread/EndThreadEnterCrititcalSection/LeaveCriticalSection
Код:
procedure ProcedureForAllThreads(); |
Цитата:
Ну и когда захотеться оптимизировать свою работы, создать класс для работы с потоками под. специфические задачи легче унаследоваца от TThread чем пелить велосипед и отлаживать его. TThread это прежде всего абстракция над апи потоками дабы программист думал как реализовать функции которые будут в потоке а не как их запилить. Ну и разработка с TThread быстрее осуществляются и по качеству апи потокам не уступает. К томуже кучу классов готовых придумали дабы сэкономить наше время. А где CloseHandle(thread)? Уу все утечка хэндлов.... На мдсне не нашел строчек о том что TerminateThread хэндл закрывает.. Код:
procedure ProcedureForAllThreads();Лучше через ExitThread(); |
А чего именно плохого в закрытии потока через TerminateThread?
Код показан только для примера |
А чего именно плохого в закрытии потока через TerminateThread?
Цитата:
И чем вообще плохо на апи потоки юзать, нету исключений, проверять рез. выполнения каждой функции ка кто напряжено. |
Цитата:
|
Цитата:
Но у меня обычно на BeginThread код так. Код:
procedure;Цитата:
|
Забыл совсем, что речь о делфи. Не пишу, поэтому не знаю, может там как-то по-другому.
|
Нового не нашел ничего, при чтении/изучении кода вероятность запутаться больше, нежели что-то понять, особенно для новичков. Был бы благодарен, за четкую, статейку с подробным описание и примерами по синхронизации потоков.
За старания +. |
работает , даже в memo записывает ,
.SpoilerTarget" type="button">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 |
| Время: 08:11 |