HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   ANTICHAT > ИНФО > Статьи
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 30.03.2012, 16:34
Tip.the.besT
Познающий
Регистрация: 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​
 
Ответить с цитированием

  #2  
Старый 30.03.2012, 16:45
mironich
Постоянный
Регистрация: 27.02.2011
Сообщений: 733
С нами: 8003126

Репутация: 19


По умолчанию

Так запускать поток

Цитата:
Сообщение от None  
potok.Create(false);
Не, ок а если я захочу конкретный экземпляр потока прибить и. т. д. как мне до него добраца.

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

Описывать класс потока в одном юните с формой не ок.

Шаблон этот создаетcя File->New->Other->ThreadObject

Коментировать так детально код, не ок, все черты мешаються с кодом.

Цитата:
Сообщение от None  
//начинаем описывать главную процедуру потока
 
Ответить с цитированием

  #3  
Старый 31.03.2012, 06:53
Tip.the.besT
Познающий
Регистрация: 24.06.2009
Сообщений: 45
С нами: 8884536

Репутация: 0
По умолчанию

Цитата:
Сообщение от mironich  
Так запускать поток
Не, ок а если я захочу конкретный экземпляр потока прибить и. т. д. как мне до него добраца.
Synhronize гдето писали что лучше его не использовать для работы с глоб. переменными а только для синхронизации работы с формой.
Описывать класс потока в одном юните с формой не ок.
Шаблон этот создаетcя File->New->Other->ThreadObject
Коментировать так детально код, не ок, все черты мешаються с кодом.
Ну с тем, что я не добавил потоки в массив я согласен, статью дополнил.

Не знаю где писали про Synhronize, но если ты запустишь мой пример, то убедишься, что нормально всё с переменными.

Интересно и как же описания потока в одном юните повлияет на работу программы?

Конечно, тебе комментарии не нужны. А человеку придётся возвращаться выше и перечитывать, что бы понять.

Итого. Достаточно было написать: "Добавь потоки в массив."
 
Ответить с цитированием

  #4  
Старый 31.03.2012, 07:06
M_script
Новичок
Регистрация: 04.11.2004
Сообщений: 5
С нами: 11322426

Репутация: 0
По умолчанию

Цитата:
Сообщение от Tip.the.besT  
Интересно и как же описания потока в одном юните повлияет на работу программы?
Пиши весь код в одну строку, это тоже не повлияет на работу программы.
 
Ответить с цитированием

  #5  
Старый 31.03.2012, 07:30
mironich
Постоянный
Регистрация: 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 членам класса.

А память то не освобождаеца....

Это самая большая ошибка, неофиты врятли ее увидят.

И будут думать после нных запусков куда делась память???
 
Ответить с цитированием

  #6  
Старый 31.03.2012, 07:42
M_script
Новичок
Регистрация: 04.11.2004
Сообщений: 5
С нами: 11322426

Репутация: 0
По умолчанию

Цитата:
Сообщение от mironich  
классы из того юнита в котором объявлен твой поток будут иметь доступ к Private членам класса
уверен?

"Директива Private начинает раздел данных (полей) и подпрограмм (методы) класса, которые являются частными (внутренними) для этого класса."
 
Ответить с цитированием

  #7  
Старый 31.03.2012, 07:48
Tip.the.besT
Познающий
Регистрация: 24.06.2009
Сообщений: 45
С нами: 8884536

Репутация: 0
По умолчанию

Цитата:
Сообщение от mironich  
НУ я имел введу коментари,
Достаточно прокомментировать в объявлении класса.
Подобный комментарий меня вводит в ступор, человека знающего азы делфи, что же будет с новичком?
Зачем переопределять конструктор??
Если в нем нечего не происходит?
А в private секции нельзя?
А нужно в приват, это правило хорошего тона что не юзаеться напрямую извне совать в привате.
И одна из таких кому то мб и не покажеться проблемой.
Когда мы объявляем данные в Private секции они доступны классам и т д,
объявленным в том же юните, соответственно безопасность работы с классом нарушается, ну и автоподстройка будет выдавать приват члены класса, а на сонную голову из-за этого можно потом долго ошибку ловить.
Ну и одно из важнейших замечаний при работе с потоками ты не указал надо обрабатывать все Except-шены, иначе поток навернется и память потечет рекой....
Label тоже его может вызвать.,.
Массив для работы с потоками тоже не лучший вариант я об об этом узнал правда только сегодня днем..
TObjectList ок вариант, точнее наследник от него.
Длинные/много строчные комментарии лучше писать так.
{Комент....
Комент,..}
Ну и ты не написал о том что это абстрактный класс и от него всегда надо наследоваца.
Об этом я написал выше, но в сокращенном виде звучит так, классы из того юнита в котором объявлен твой поток будут иметь доступ к Private членам класса.
А память то не освобождаеца....
Это самая большая ошибка, неофиты врятли ее увидят.
И будут думать после нных запусков куда делась память???
Если бы ты читал не поверхностно, то возможно заметил бы следующие:

Цитата:
Сообщение от None  
Под implementation нам надо описать конструкцию потока, в данном примере это не обязательно, но, что бы вы знали на будущие мы всё равно это сделаем.
Каждый решает для себя как оформлять код, и честно говоря я бы лучше послушал комментарии тех людей которые разбираются с потоками по этой статье, и они бы сказали, что им конкретно не понятно.

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

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

  #8  
Старый 31.03.2012, 08:05
mironich
Постоянный
Регистрация: 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;
Тесть получаем доступ к приват члену в обход свойств(если бы были).

Если все это объявлено в одном юните.
 
Ответить с цитированием

  #9  
Старый 31.03.2012, 09:10
Unknown
Новичок
Регистрация: 21.06.2005
Сообщений: 1
С нами: 10992741

Репутация: 0
По умолчанию

а не проще & лучше вместо TThread юзать апи потоки? (beginthread - createthread)
 
Ответить с цитированием

  #10  
Старый 31.03.2012, 09:17
mironich
Постоянный
Регистрация: 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)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.