 |
|

30.03.2012, 16:34
|
|
Познающий
Регистрация: 24.06.2009
Сообщений: 45
С нами:
8884536
Репутация:
0
|
|
Реализация многопоточности в delphi. Вижу множество вопросов на разных форумах по программированию которые относятся к многопоточности. В этой статье я бы хотел не только объяснить как реализовать это в delphi, но и предоставить готовый шаблон многопоточного приложения который вы бы могли использовать в дальнейшем в своих программах.
Для начала немного теории. Одной из важнейших вещей в многопоточном приложении является синхронизация потоков. Если не учесть, синхронизацию потоков, то это, может привести к плачевным последствиям. Давайте рассмотрим такой пример, после чего у вас в голове всё встанет на свои места.
У нас в программе имеется переменная, назовём её - "int" - типа integer. Если мы будем присваивать ей значение из основного потока, то всё будет хорошо, и проблем не возникнет. Мало того, если даже мы присвоим ей значение из дополнительного потока который мы создали, то всё будет хорошо. Но так как зачастую у многопоточного приложения более одного дополнительного потока, то зададимся вопросом- "А, что будет если мы попытаемся присвоить переменной значение из двух потоков одновременно?". Вот тут, и произойдёт то таинственное события при котором переменной присвоится значение которое скорее всего не будет равно ни одному из значений которых вы планировали ей присвоить. Тогда и вступает в игру метод синхронизации synchronize. С помощь данного нам разработчиками delphi метода, вы можете обеспечить безопасное обращение нескольких потоков к одной переменной. Данный метод обеспечивает следующие: при вызове процедуры с помощью synchronize она начинает выполнятся, а все остальные процедуры вызванные с помощью этого метода встают в очередь и ждут выполнения текущей процедуры. Всё вышеописанное относится только к записи переменных, а читать можно одновременно из нескольких потоков и ничего плохого не произойдёт. Ну а теперь перейдём к практике.
И так создадим новый проект. И начнём писать поток. После вот этих строк:
Код:
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;
Ну здесь вроде бы всё понятно, вы можете добавлять сколько угодно переменных и процедур для удобства работы.
Под implementation нам надо описать конструкцию потока, в данном примере это не обязательно, но, что бы вы знали на будущие мы всё равно это сделаем.
Код:
constructor potok.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);//Эта строка говорит о том, что поток после создания
//будет приостановлен если ему передать значение true при создание, если false, то сразу
//начнёт работу.
end;
Добавим в глобальные переменные переменную - "nom" - типа integer, а так же массив наших потоков.
Код:
var
Form1: TForm1;
nom:integer;
a: array [1..10] of potok;//массив для хранения наших потоков
Теперь нужно описать процедуры потока, под implementation добавляем следующие:
Код:
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;
Собственно всё готово, остаются только запустить потоки. Кидаем кнопку на форму, "кастуем на неё даблклик" и пишем:
Код:
procedure TForm1.Button1Click(Sender: TObject);
var
pot:integer;
begin
for pot:=1 to 10 do //цикл запускает 10 потоков, которые будут изменять заголовок
a[pot]:=potok.Create(false); //формы, так же идёт
//добавление в массив, что бы потом вы могли уничтожить
//один поток.
end;
Вот так выглядит программа в итоге:
Код:
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
|
|
|

30.03.2012, 16:45
|
|
Постоянный
Регистрация: 27.02.2011
Сообщений: 733
С нами:
8003126
Репутация:
19
|
|
Так запускать поток
Сообщение от None
potok.Create(false);
Не, ок а если я захочу конкретный экземпляр потока прибить и. т. д. как мне до него добраца.
Synhronize гдето писали что лучше его не использовать для работы с глоб. переменными а только для синхронизации работы с формой.
Описывать класс потока в одном юните с формой не ок.
Шаблон этот создаетcя File->New->Other->ThreadObject
Коментировать так детально код, не ок, все черты мешаються с кодом.
Сообщение от None
//начинаем описывать главную процедуру потока
|
|
|

31.03.2012, 06:53
|
|
Познающий
Регистрация: 24.06.2009
Сообщений: 45
С нами:
8884536
Репутация:
0
|
|
Сообщение от mironich
Так запускать поток
Не, ок а если я захочу конкретный экземпляр потока прибить и. т. д. как мне до него добраца.
Synhronize гдето писали что лучше его не использовать для работы с глоб. переменными а только для синхронизации работы с формой.
Описывать класс потока в одном юните с формой не ок.
Шаблон этот создаетcя File->New->Other->ThreadObject
Коментировать так детально код, не ок, все черты мешаються с кодом.
Ну с тем, что я не добавил потоки в массив я согласен, статью дополнил.
Не знаю где писали про Synhronize, но если ты запустишь мой пример, то убедишься, что нормально всё с переменными.
Интересно и как же описания потока в одном юните повлияет на работу программы?
Конечно, тебе комментарии не нужны. А человеку придётся возвращаться выше и перечитывать, что бы понять.
Итого. Достаточно было написать: "Добавь потоки в массив."
|
|
|

31.03.2012, 07:06
|
|
Новичок
Регистрация: 04.11.2004
Сообщений: 5
С нами:
11322426
Репутация:
0
|
|
Сообщение от Tip.the.besT
Интересно и как же описания потока в одном юните повлияет на работу программы?
Пиши весь код в одну строку, это тоже не повлияет на работу программы.
|
|
|

31.03.2012, 07:30
|
|
Постоянный
Регистрация: 27.02.2011
Сообщений: 733
С нами:
8003126
Репутация:
19
|
|
Сообщение от None
Конечно, тебе комментарии не нужны. А человеку придётся возвращаться выше и перечитывать, что бы понять.
НУ я имел введу коментари,
Сообщение от None
procedure potok.Execute;//начинаем описывать главную процедуру потока
Достаточно прокомментировать в объявлении класса.
Сообщение от None
synchronize(synchro);//этой строкой мы вызываем процедуру synchro в единичном экземпляре
Подобный комментарий меня вводит в ступор, человека знающего азы делфи, что же будет с новичком?
Зачем переопределять конструктор??
Если в нем нечего не происходит?
Сообщение от None
procedure synchro;//в разделе public вы можете объявить процедуры какие только душе
//угодно
А в private секции нельзя?
А нужно в приват, это правило хорошего тона что не юзаеться напрямую извне совать в привате.
И одна из таких кому то мб и не покажеться проблемой.
Когда мы объявляем данные в Private секции они доступны классам и т д,
объявленным в том же юните, соответственно безопасность работы с классом нарушается, ну и автоподстройка будет выдавать приват члены класса, а на сонную голову из-за этого можно потом долго ошибку ловить.
Ну и одно из важнейших замечаний при работе с потоками ты не указал надо обрабатывать все Except-шены, иначе поток навернется и память потечет рекой....
Label тоже его может вызвать.,.
Массив для работы с потоками тоже не лучший вариант я об об этом узнал правда только сегодня днем..
TObjectList ок вариант, точнее наследник от него.
Длинные/много строчные комментарии лучше писать так.
{Комент....
Комент,..}
Ну и ты не написал о том что это абстрактный класс и от него всегда надо наследоваца.
Сообщение от None
Интересно и как же описания потока в одном юните повлияет на работу программы?
Об этом я написал выше, но в сокращенном виде звучит так, классы из того юнита в котором объявлен твой поток будут иметь доступ к Private членам класса.
А память то не освобождаеца....
Это самая большая ошибка, неофиты врятли ее увидят.
И будут думать после нных запусков куда делась память???
|
|
|

31.03.2012, 07:42
|
|
Новичок
Регистрация: 04.11.2004
Сообщений: 5
С нами:
11322426
Репутация:
0
|
|
Сообщение от mironich
классы из того юнита в котором объявлен твой поток будут иметь доступ к Private членам класса
уверен?
"Директива Private начинает раздел данных (полей) и подпрограмм (методы) класса, которые являются частными (внутренними) для этого класса."
|
|
|

31.03.2012, 07:48
|
|
Познающий
Регистрация: 24.06.2009
Сообщений: 45
С нами:
8884536
Репутация:
0
|
|
Сообщение от mironich
НУ я имел введу коментари,
Достаточно прокомментировать в объявлении класса.
Подобный комментарий меня вводит в ступор, человека знающего азы делфи, что же будет с новичком?
Зачем переопределять конструктор??
Если в нем нечего не происходит?
А в private секции нельзя?
А нужно в приват, это правило хорошего тона что не юзаеться напрямую извне совать в привате.
И одна из таких кому то мб и не покажеться проблемой.
Когда мы объявляем данные в Private секции они доступны классам и т д,
объявленным в том же юните, соответственно безопасность работы с классом нарушается, ну и автоподстройка будет выдавать приват члены класса, а на сонную голову из-за этого можно потом долго ошибку ловить.
Ну и одно из важнейших замечаний при работе с потоками ты не указал надо обрабатывать все Except-шены, иначе поток навернется и память потечет рекой....
Label тоже его может вызвать.,.
Массив для работы с потоками тоже не лучший вариант я об об этом узнал правда только сегодня днем..
TObjectList ок вариант, точнее наследник от него.
Длинные/много строчные комментарии лучше писать так.
{Комент....
Комент,..}
Ну и ты не написал о том что это абстрактный класс и от него всегда надо наследоваца.
Об этом я написал выше, но в сокращенном виде звучит так, классы из того юнита в котором объявлен твой поток будут иметь доступ к Private членам класса.
А память то не освобождаеца....
Это самая большая ошибка, неофиты врятли ее увидят.
И будут думать после нных запусков куда делась память???
Если бы ты читал не поверхностно, то возможно заметил бы следующие:
Сообщение от None
Под implementation нам надо описать конструкцию потока, в данном примере это не обязательно, но, что бы вы знали на будущие мы всё равно это сделаем.
Каждый решает для себя как оформлять код, и честно говоря я бы лучше послушал комментарии тех людей которые разбираются с потоками по этой статье, и они бы сказали, что им конкретно не понятно.
В размещении кода в одном юните я не вижу ничего страшного. А вот утечку памяти я не учёл, так как я уделил внимание именно потокам и организации их создания. Про массив тоже слышу первый раз. И пока не сталкивался с моментами в которых он работает нестабильно.
Исключения я с удовольствием добавлю. Так же считаю, что пора завязывать с этим спором, так как каждый будет стоять на своём.
|
|
|

31.03.2012, 08:05
|
|
Постоянный
Регистрация: 27.02.2011
Сообщений: 733
С нами:
8003126
Репутация:
19
|
|
Сообщение от None
уверен?
Немного ошибся из других классов доступ не получить(напрямую к полю), а вот у создананого экземпляра можно.
Код:
SimpleClass = class
private G: string;
end;
Код:
var
Form1: TForm1;
H : SimpleClass;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
H.G := 'g';
end;
Тесть получаем доступ к приват члену в обход свойств(если бы были).
Если все это объявлено в одном юните.
|
|
|

31.03.2012, 09:10
|
|
Новичок
Регистрация: 21.06.2005
Сообщений: 1
С нами:
10992741
Репутация:
0
|
|
а не проще & лучше вместо TThread юзать апи потоки? (beginthread - createthread)
|
|
|

31.03.2012, 09:17
|
|
Постоянный
Регистрация: 27.02.2011
Сообщений: 733
С нами:
8003126
Репутация:
19
|
|
Сообщение от C00LPack
а не проще вместо TThread юзать апи потоки? (beginthread - createthread)
Не проще особенно когда, ценишь время, тут в два клика создается класс почти готовый к работе, в который можно передавать данные без напрягов, не через глобальные переменные.
С BeginThread для прямой передачи данных надо описывать свой тип передавать в него данные...
Я пользовался сначала BeginThread когда попробовал TThread(научился с ним работать), до этого я не вьежал как переопределить метод и многово не понимал...
Я потом окуел сколько я тратил времени на создание потоков через BeginThread...(В плане велосипедов для передачи данных в поток )
Сча вот пишу компонент чтоб кинул на форму, указатели на форму и нужные классы дал и готово..
И самое главное использование TThread это ООП стиль программирования(если вся программа в нем написана), а BeginThread\CreatThread Функциональный\процедурны (я различие обоих стилей не знаю(функц.проц.)).
Ну и это опять же увеличение повторного использования кода.
Вместо BeginThread(Параметры);
Я зделаю, Thread := TThread.Create(замороженный\не замороженный);
Конечно можно выполнить создание через макросы, но как по мне целесообразней юзать TThread для десктоп софта, не для ботов и.т.д.
|
|
|
|
 |
|
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|