Авторские статьи Укращение страптивых, или синхронизация потоков в Делфи

Discussion in 'Статьи' started by shadowrun, 29 Aug 2012.

  1. shadowrun

    shadowrun Banned

    Joined:
    29 Aug 2010
    Messages:
    842
    Likes Received:
    170
    Reputations:
    84


    [Предисловие]​


    В своей практике частенько встречаюсь с необходимостью написания многопоточных приложений. Вот решил поделиться опытом и показать основные краеугольные камни синхронизации потов. Было у меня время, когда при упоминании многопоточности я обливался холодным потом, а мое анальное отверстие сжималось с такой силой, что могло спокойно перекусить металлический лом, или кусок арматуры. У большинства начинающих кодеров, которые впервые слышат о потоках ощущения примерно те-же. Вот для вас господа я то и стараюсь ;) . Предупреждаю сразу: я не експерт и не гуру кодинга, все что буду здесь писать основано на личном опыте. Если будут критические замечания или дельные советы, судовольствием добавлю их в сей текст.



    [Нах оно нужно?]​


    Вообще-то статейка направлена на кодеров, которые уже в курсе что такое потоки и как создавать поток. Но все-же скажу пару мотивирующих слов... Основное преимущество многопоточного приложения - это скорость выполнения работы. Я преимущественно имею дело с приложениями, которые работают через вэб интерфейсы и по сравнению с однопоточными, приложения работающие в несколько потоков по скорости выигрывают в разы. Ну воды уже налил достаточно, подошло дело к практике :cool:.



    [Кодинг]​


    ТЗ: Создать многопоточный парсер имени и фамилии социальной сети ВКонтакте по списку ID.

    Своей извращенной фантазией я представил данное приложение в таком виде:

    [​IMG]

    1 - Поле мемо (DigitLst) со списком чисел ID.
    2 - Поле мемо (DataLst) со списком спаршенных имен.
    3 - Для генерации чисел набросал простенькую процедурку:
    PHP:
    procedure TMainFrm.Button1Click(SenderTObject);
    var 
    I,Jinteger;
        
    SString;
    begin
     Randomize
    ;
     
    DigitLst.Clear;
        for 
    := 1 to StrToInt(Edit2.Text) do
          
    begin
            
    for := 1 to StrToInt(Edit1.text) do
              
    := IntToStr(Random(10));
            
    DigitLst.Lines.Add(S);
            
    := '';
          
    end;
     
    SL := TStringList.Create;
     
    SL.AddStrings(DigitLst.Lines);
    end;
    сильно углубляться не стану, скажу лишь, что значение Edit2.Text отвечает за количество сгенерированных чисел а Edit1.text за количество цифр в числе. Прошу обратить внимание на строки
    SL := TStringList.Create;
    SL.AddStrings(DigitLst.Lines);
    Я создаю список SL и построчно подгружаю в него значение нашего мемо со сгенерированными числами (DigitLst.Lines). Дело в том, что с этим списком мы будем работать из потоков, а так как мемо VCL компонент я не хочу загромождать код дополнительными моментами синхронизации.

    Собственно прежде чем перейдем к созданию потока, попрошу уяснить одну простую вещь:

    СИНХРОНИЗАЦИЯ - ЭТО ОЧЕНЬ ПРОСТО!


    Давайте рассмотрим алгоритм работы потока:
    1 - взять определенный Id из SL.
    2 - удалить выбранный Id, дабы другие потоки не получили его значение.
    3 - Отправить запрос вконтактику, получить ответ, спарсить необходимый результат.
    4 - Записать полученный результат в мемо на форме
    5 - если в списке нет строк - закончить работу.

    Из этого списка могут вызвать траблы пункт 1,2,4. Проблемы с пунктами 1,2 могут возникнуть в том случае, если несколько потоков одновременно возьмут/удалят одно и то-же значение из списка. В пункте 4 чревато обращение нескольких потоков к компоненту VCL. Как избежать этих проблем? Нужно просто усвоить некоторые правила работы с потоками:



    1. Переменная в которой содержаться данные используемые потоками должна быть глобальной.

    2. Чтобы избежать проблем при работе с глобальными переменными используются критические секции TCriticalSection.

    3. Для работы с глобальными переменными непосредственно в потоке нужно использовать поля класса потока (переменные объявленные в разделе private).

    4. Если поток обращается к любому визуальному компоненту необходимо использовать процедуру Synchronize, аргументом которой будет процедура обращения к визуалу без параметров.


    PHP:
    unit Unit2;

    interface

    uses
      Classes
    ,IdHttp

    type
      ParsThrd 
    = class(TThread)
      private         
        
    FCurrLnkString// поле, доступно данному потоку для хранения значения текущего ID
        
    FtmpString//поле для передачи данных VCL объекту
      
    protected
        
    procedure Executeoverride;
        
    procedure UpdMemo// метод без параметров для процедуры Synchronize
      
    end;

    implementation

    uses Unit1
    IdHTTPHeaderInfo;

    var 
    DoWorkBoolean True// переменная которая оборвет цикл, если в списке с ID закончатся строки. 

    procedure ParsThrd.UpdMemo// описание метода записывающего результат в мемо формы.
    begin
      MainFrm
    .DataLst.Lines.Add(Ftmp);
    end;

    procedure ParsThrd.Execute;
    var 
    HttpTIdhttp;
          
    STTStringStream// Переменная в которую будет писаться ответ сервера, дабы не было проблем с русскими буквами.

    begin
     
    while DoWork True do //зацикливаем работу потока
     
    begin

    //CS: TCriticalSection;
    //ниже мы используем критическую секцию.
    // Код расположен в блоке CS.Enter  CS.Leave не будет сеиваться между потоками.
    // Здесь мы проверяем на наличие строк в списке, присваиваем значение строки полю FCurrLnk и удаляем эту строку из списка.
    // Если не использовать крит. секцию то между потоками возникнет своеобразный хаос,
    // с нежелательными последствиями
      
    CS.Enter;
        if 
    SL.Count <> 0
          then
            begin
             
    try
               
    FCurrLnk := Sl[0];
               
    SL.Delete(0);
            finally
              
    CS.Leave;
            
    end

          
    else

            
    begin
              CS
    .Leave;
              
    DoWork := False;
              Exit;
            
    end;




    // На данном участке кода формируем более-менее приемлимые заголовки, отсылаем запрос и парсим результат.
         
    Http := TIdHTTP.Create(nil);
          
    with Http.Request do
            
    begin
              UserAgent 
    := 'Mozilla/5.0 (Windows NT 5.1; rv:14.0) Gecko/20100101 Firefox/14.0.1';
              
    Host := 'vk.com';
              
    AcceptLanguage := 'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3';
              
    ContentType := 'text/html; charset=windows-1251';
            
    end;
         
    ST := TStringStream.Create('');
         try
           
    Http.Get('http://vk.com/id'+FCurrLnk,ST);
           
    Http.Disconnect;
           
    Ftmp := ST.DataString;
         finally
           
    http.Free;
           
    ST.Free
         end
    ;
        
    Ftmp := Copy(Ftmp,Pos('<title>',Ftmp) + 7,Pos('</title>',Ftmp) - Pos('<title>',Ftmp) -);
        
    Synchronize(UpdMemo); // Синхронизируем обращение потока к мемо.
       
    end;
     
    end;

    end.
    Код постарался привести к максимально читабельному виду. Если что не понятно, спрашивайте, буду рад ответить.


    [Эпилог]​


    Повторюсь еще раз: описанный мною метод не самый оптимальный и взят из личного опыта, но он работает. Буду рад разумной критике и предложениям, если есть что дополнить, не проходите мимо. Софт + исходники созданы исключительно в образовательных целях.
    ____________

    Особая благодарность за консультации и поддержку: DooD , mironich

    софт + сурсы
     
    #1 shadowrun, 29 Aug 2012
    Last edited: 30 Aug 2012
    3 people like this.
  2. mironich

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

    Joined:
    27 Feb 2011
    Messages:
    733
    Likes Received:
    73
    Reputations:
    19
    Лучше раз и в конструкторе, чем каждый раз создавать.
    А так я рад что ты таки написал ее.
    ЗЫ есче всегда выходить из крит секции надо в try: finally: иначе искл. поймаем и не выйдем.
     
    #2 mironich, 29 Aug 2012
    Last edited: 29 Aug 2012
    1 person likes this.
  3. shadowrun

    shadowrun Banned

    Joined:
    29 Aug 2010
    Messages:
    842
    Likes Received:
    170
    Reputations:
    84
    Спасибо, добавил.