PDA

Просмотр полной версии : Укращение страптивых, или синхронизация потоков в Делфи


shadowrun
29.08.2012, 22:05
[Предисловие]​

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



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

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



[Кодинг]​

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

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

http://i049.radikal.ru/1208/3f/0af34c1ab0b9.jpg ​

1 - Поле мемо (DigitLst) со списком чисел ID.

2 - Поле мемо (DataLst) со списком спаршенных имен.

3 - Для генерации чисел набросал простенькую процедурку:


procedure TMainFrm.Button1Click(Sender:TObject);

varI,J:integer;

S:String;

begin

Randomize;

DigitLst.Clear;

forI:=1 to StrToInt(Edit2.Text) do

begin

forJ:=1 to StrToInt(Edit1.text) do

S:=S+IntToStr(Random(10));

DigitLst.Lines.Add(S);

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, аргументом которой будет процедура обращения к визуалу без параметров.




unit Unit2;

interface

uses

Classes,IdHttp;

type

ParsThrd= class(TThread)

private

FCurrLnk:String;// поле, доступно данному п току для хранения значен я текущего ID

Ftmp:String;//поле для передачи данных VCL объекту

protected

procedure Execute;override;

procedure UpdMemo;// метод без параметров для процедуры Synchronize

end;

implementation

uses Unit1,IdHTTPHeaderInfo;

varDoWork:Boolean=True;// переменная которая оборв ет цикл, если в списке с I D закончатся строки.

procedure ParsThrd.UpdMemo;// описание метода записыва ющего результат в мемо фо рмы.

begin

MainFrm.DataLst.Lines.Add(Ftmp);

end;

procedure ParsThrd.Execute;

varHttp:TIdhttp;

ST:TStringStream;// Переменная в которую буд ет писаться ответ сервера , дабы не было проблем с усскими буквами.

begin

whileDoWork=Truedo//зацикливаем работу потока

begin

//CS: TCriticalSection;

//ниже мы используем критич ескую секцию.

// Код расположен в блоке CS .Enter CS.Leave не будет сеиват ься между потоками.

// Здесь мы проверяем на на личие строк в списке, при ваиваем значение строки олю FCurrLnk и удаляем эту с року из списка.

// Если не использовать кри т. секцию то между потока и возникнет своеобразный хаос,

// с нежелательными последс твиями

CS.Enter;

ifSL.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.Requestdo

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('',Ftmp) +7,Pos('',Ftmp) -Pos('',Ftmp) -7);

Synchronize(UpdMemo);// Синхронизируем обращение потока к мемо.

end;

end;

end.

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



[Эпилог]​

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

____________

Особая благодарность за консультации и поддержку: DooD (https://antichat.live/member.php/u/139998/) , mironich (https://antichat.live/member.php/u/155917/)



софт + сурсы (http://www.sendspace.com/file/ydvix1)

mironich
29.08.2012, 22:11
Http := TIdHTTP.Create(nil);


Лучше раз и в конструкторе, чем каждый раз создавать.

А так я рад что ты таки написал ее.

ЗЫ есче всегда выходить из крит секции надо в try: finally: иначе искл. поймаем и не выйдем.

shadowrun
30.08.2012, 11:06
ЗЫ есче всегда выходить из крит секции надо в try: finally: иначе искл. поймаем и не выйдем.


Спасибо, добавил.