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)
В своей практике частенько встречаюсь с необходимостью написания многопоточных приложений. Вот решил поделиться опытом и показать основные краеугольные камни синхронизации потов. Было у меня время, когда при упоминании многопоточности я обливался холодным потом, а мое анальное отверстие сжималось с такой силой, что могло спокойно перекусить металлический лом, или кусок арматуры. У большинства начинающих кодеров, которые впервые слышат о потоках ощущения примерно те-же. Вот для вас господа я то и стараюсь . Предупреждаю сразу: я не експерт и не гуру кодинга, все что буду здесь писать основано на личном опыте. Если будут критические замечания или дельные советы, судовольствием добавлю их в сей текст.
[Нах оно нужно?]
Вообще-то статейка направлена на кодеров, которые уже в курсе что такое потоки и как создавать поток. Но все-же скажу пару мотивирующих слов... Основное преимущество многопоточного приложения - это скорость выполнения работы. Я преимущественно имею дело с приложениями, которые работают через вэб интерфейсы и по сравнению с однопоточными, приложения работающие в несколько потоков по скорости выигрывают в разы. Ну воды уже налил достаточно, подошло дело к практике .
[Кодинг]
ТЗ: Создать многопоточный парсер имени и фамилии социальной сети ВКонтакте по списку 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)