Авторские статьи Реализация многопоточности [delphi]

Discussion in 'Статьи' started by Tip.the.besT, 30 Mar 2012.

  1. Tip.the.besT

    Tip.the.besT Member

    Joined:
    24 Jun 2009
    Messages:
    267
    Likes Received:
    10
    Reputations:
    4
    Реализация многопоточности в 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​
     
    #1 Tip.the.besT, 30 Mar 2012
    Last edited: 31 Mar 2012
    guest11, Juy0 and shadowrun like this.
  2. mironich

    mironich Elder - Старейшина

    Joined:
    27 Feb 2011
    Messages:
    733
    Likes Received:
    73
    Reputations:
    19
    Так запускать поток
    Не, ок а если я захочу конкретный экземпляр потока прибить и. т. д. как мне до него добраца.

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

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

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

    Коментировать так детально код, не ок, все черты мешаються с кодом.
     
    1 person likes this.
  3. Tip.the.besT

    Tip.the.besT Member

    Joined:
    24 Jun 2009
    Messages:
    267
    Likes Received:
    10
    Reputations:
    4
    Ну с тем, что я не добавил потоки в массив я согласен, статью дополнил.

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

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

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

    Итого. Достаточно было написать: "Добавь потоки в массив."
     
  4. M_script

    M_script Members of Antichat

    Joined:
    4 Nov 2004
    Messages:
    2,581
    Likes Received:
    1,317
    Reputations:
    1,557
    Пиши весь код в одну строку, это тоже не повлияет на работу программы.
     
  5. mironich

    mironich Elder - Старейшина

    Joined:
    27 Feb 2011
    Messages:
    733
    Likes Received:
    73
    Reputations:
    19
    НУ я имел введу коментари,

    Достаточно прокомментировать в объявлении класса.


    Подобный комментарий меня вводит в ступор, человека знающего азы делфи, что же будет с новичком?

    Зачем переопределять конструктор??
    Если в нем нечего не происходит?

    А в private секции нельзя?
    А нужно в приват, это правило хорошего тона что не юзаеться напрямую извне совать в привате.

    И одна из таких кому то мб и не покажеться проблемой.

    Когда мы объявляем данные в Private секции они доступны классам и т д,
    объявленным в том же юните, соответственно безопасность работы с классом нарушается, ну и автоподстройка будет выдавать приват члены класса, а на сонную голову из-за этого можно потом долго ошибку ловить.

    Ну и одно из важнейших замечаний при работе с потоками ты не указал надо обрабатывать все Except-шены, иначе поток навернется и память потечет рекой....

    Label тоже его может вызвать.,.

    Массив для работы с потоками тоже не лучший вариант я об об этом узнал правда только сегодня днем..
    TObjectList ок вариант, точнее наследник от него.

    Длинные/много строчные комментарии лучше писать так.

    {Комент....
    Комент,..}

    Ну и ты не написал о том что это абстрактный класс и от него всегда надо наследоваца.

    Об этом я написал выше, но в сокращенном виде звучит так, классы из того юнита в котором объявлен твой поток будут иметь доступ к Private членам класса.

    А память то не освобождаеца....
    Это самая большая ошибка, неофиты врятли ее увидят.
    И будут думать после нных запусков куда делась память???
     
    #5 mironich, 31 Mar 2012
    Last edited: 31 Mar 2012
    1 person likes this.
  6. M_script

    M_script Members of Antichat

    Joined:
    4 Nov 2004
    Messages:
    2,581
    Likes Received:
    1,317
    Reputations:
    1,557
    уверен?

    "Директива Private начинает раздел данных (полей) и подпрограмм (методы) класса, которые являются частными (внутренними) для этого класса."
     
    #6 M_script, 31 Mar 2012
    Last edited: 31 Mar 2012
  7. Tip.the.besT

    Tip.the.besT Member

    Joined:
    24 Jun 2009
    Messages:
    267
    Likes Received:
    10
    Reputations:
    4
    Если бы ты читал не поверхностно, то возможно заметил бы следующие:
    Каждый решает для себя как оформлять код, и честно говоря я бы лучше послушал комментарии тех людей которые разбираются с потоками по этой статье, и они бы сказали, что им конкретно не понятно.

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

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

    mironich Elder - Старейшина

    Joined:
    27 Feb 2011
    Messages:
    733
    Likes Received:
    73
    Reputations:
    19
    Немного ошибся из других классов доступ не получить(напрямую к полю), а вот у создананого экземпляра можно.

    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;
    Тесть получаем доступ к приват члену в обход свойств(если бы были).
    Если все это объявлено в одном юните.
     
  9. C00LPack

    C00LPack Elder - Старейшина

    Joined:
    7 Oct 2010
    Messages:
    425
    Likes Received:
    72
    Reputations:
    19
    а не проще & лучше вместо TThread юзать апи потоки? (beginthread - createthread)
     
  10. mironich

    mironich Elder - Старейшина

    Joined:
    27 Feb 2011
    Messages:
    733
    Likes Received:
    73
    Reputations:
    19
    Не проще особенно когда, ценишь время, тут в два клика создается класс почти готовый к работе, в который можно передавать данные без напрягов, не через глобальные переменные.
    С BeginThread для прямой передачи данных надо описывать свой тип передавать в него данные...
    Я пользовался сначала BeginThread когда попробовал TThread(научился с ним работать), до этого я не вьежал как переопределить метод и многово не понимал...
    Я потом окуел сколько я тратил времени на создание потоков через BeginThread...(В плане велосипедов для передачи данных в поток )
    Сча вот пишу компонент чтоб кинул на форму, указатели на форму и нужные классы дал и готово..
    И самое главное использование TThread это ООП стиль программирования(если вся программа в нем написана), а BeginThread\CreatThread Функциональный\процедурный(я различие обоих стилей не знаю(функц.проц.)).
    Ну и это опять же увеличение повторного использования кода.
    Вместо BeginThread(Параметры);
    Я зделаю, Thread := TThread.Create(замороженный\не замороженный);
    Конечно можно выполнить создание через макросы, но как по мне целесообразней юзать TThread для десктоп софта, не для ботов и.т.д.
     
    #10 mironich, 31 Mar 2012
    Last edited: 31 Mar 2012
  11. M_script

    M_script Members of Antichat

    Joined:
    4 Nov 2004
    Messages:
    2,581
    Likes Received:
    1,317
    Reputations:
    1,557
    Чем чистый апи может быть проще или лучше обертки?
     
  12. ChymeNik

    ChymeNik Member

    Joined:
    31 Aug 2010
    Messages:
    29
    Likes Received:
    7
    Reputations:
    9
    По мне гораздо легче использовать BeginThread/EndThreadEnterCrititcalSection/LeaveCriticalSection
    Code:
    procedure ProcedureForAllThreads();
    begin
    EnterCriticalSection(CS);
    //Тут располагаем код, который должен выполняться только в одном потоке
    LeaveCriticalSection(CS);
    end;
    thread:=BeginThread(nil,1024,@ProcedureForAllThreads,nil,0,ThreadIdVarCardinalType);
      TerminateThread(thread,0);
    
    
     
    #12 ChymeNik, 31 Mar 2012
    Last edited: 31 Mar 2012
  13. mironich

    mironich Elder - Старейшина

    Joined:
    27 Feb 2011
    Messages:
    733
    Likes Received:
    73
    Reputations:
    19
    Чем они тяжелы тем что не заставляют каждый раз одно и тоже писать??
    Ну и когда захотеться оптимизировать свою работы, создать класс для работы с потоками под. специфические задачи легче унаследоваца от TThread чем пелить велосипед и отлаживать его.
    TThread это прежде всего абстракция над апи потоками дабы программист думал как реализовать функции которые будут в потоке а не как их запилить.
    Ну и разработка с TThread быстрее осуществляются и по качеству апи потокам не уступает.
    К томуже кучу классов готовых придумали дабы сэкономить наше время.

    А где CloseHandle(thread)?
    Уу все утечка хэндлов....
    На мдсне не нашел строчек о том что TerminateThread хэндл закрывает..

    Code:
    procedure ProcedureForAllThreads();
    begin
    EnterCriticalSection(CS);
    //Тут располагаем код, который должен выполняться только в одном потоке
    LeaveCriticalSection(CS);
    end;
    А убивать через TerminateThread нужно только в крайних случаях, не советуйте плохого)

    Лучше через ExitThread();
     
    #13 mironich, 31 Mar 2012
    Last edited: 31 Mar 2012
  14. ChymeNik

    ChymeNik Member

    Joined:
    31 Aug 2010
    Messages:
    29
    Likes Received:
    7
    Reputations:
    9
    А чего именно плохого в закрытии потока через TerminateThread?
    Код показан только для примера
     
  15. mironich

    mironich Elder - Старейшина

    Joined:
    27 Feb 2011
    Messages:
    733
    Likes Received:
    73
    Reputations:
    19
    А чего именно плохого в закрытии потока через TerminateThread?
    (c)mdsn
    И чем вообще плохо на апи потоки юзать, нету исключений, проверять рез. выполнения каждой функции ка кто напряжено.
     
  16. M_script

    M_script Members of Antichat

    Joined:
    4 Nov 2004
    Messages:
    2,581
    Likes Received:
    1,317
    Reputations:
    1,557
    "Поток можно завершить принудительно, вызвав ExitThread. При этом освобождаются все ресурсы операционной системы, выделенные дан ному потоку, но C/C++ - pеcypcы (например, объекты, созданные из С++-классов) не очищаются. Именно поэтому лучше возвращать управление из функции потока, чем самому вызывать функцию ExitThread." (Д. Рихтер)
     
  17. mironich

    mironich Elder - Старейшина

    Joined:
    27 Feb 2011
    Messages:
    733
    Likes Received:
    73
    Reputations:
    19
    Чет я не вкурил.
    Но у меня обычно на BeginThread код так.
    Code:
    procedure;
    begin
    
    ExitThread(0);
    end;
    
    В каком понятии очистить если я принудительно вызываю перед завершением потока деструкторы?
     
  18. M_script

    M_script Members of Antichat

    Joined:
    4 Nov 2004
    Messages:
    2,581
    Likes Received:
    1,317
    Reputations:
    1,557
    Забыл совсем, что речь о делфи. Не пишу, поэтому не знаю, может там как-то по-другому.
     
  19. shadowrun

    shadowrun Banned

    Joined:
    29 Aug 2010
    Messages:
    842
    Likes Received:
    170
    Reputations:
    84
    Нового не нашел ничего, при чтении/изучении кода вероятность запутаться больше, нежели что-то понять, особенно для новичков. Был бы благодарен, за четкую, статейку с подробным описание и примерами по синхронизации потоков.
    За старания +.
     
  20. guest11

    guest11 Member

    Joined:
    4 Dec 2018
    Messages:
    32
    Likes Received:
    5
    Reputations:
    0
    работает , даже в memo записывает ,

    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.