[Предисловие]
В своей практике частенько встречаюсь с необходимостью написания многопоточных приложений. Вот решил поделиться опытом и показать основные краеугольные камни синхронизации потов. Было у меня время, когда при упоминании многопоточности я обливался холодным потом, а мое анальное отверстие сжималось с такой силой, что могло спокойно перекусить металлический лом, или кусок арматуры. У большинства начинающих кодеров, которые впервые слышат о потоках ощущения примерно те-же. Вот для вас господа я то и стараюсь . Предупреждаю сразу: я не експерт и не гуру кодинга, все что буду здесь писать основано на личном опыте. Если будут критические замечания или дельные советы, судовольствием добавлю их в сей текст.
[Нах оно нужно?]
Вообще-то статейка направлена на кодеров, которые уже в курсе что такое потоки и как создавать поток. Но все-же скажу пару мотивирующих слов... Основное преимущество многопоточного приложения - это скорость выполнения работы. Я преимущественно имею дело с приложениями, которые работают через вэб интерфейсы и по сравнению с однопоточными, приложения работающие в несколько потоков по скорости выигрывают в разы. Ну воды уже налил достаточно, подошло дело к практике .
[Кодинг]
ТЗ: Создать многопоточный парсер имени и фамилии социальной сети ВКонтакте по списку ID.
Своей извращенной фантазией я представил данное приложение в таком виде:
1 - Поле мемо (DigitLst) со списком чисел ID.
2 - Поле мемо (DataLst) со списком спаршенных имен.
3 - Для генерации чисел набросал простенькую процедурку:
PHP код:
[COLOR="#000000"][COLOR="#0000BB"]procedure TMainFrm[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Button1Click[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]Sender[/COLOR][COLOR="#007700"]:[/COLOR][COLOR="#0000BB"]TObject[/COLOR][COLOR="#007700"]);
var[/COLOR][COLOR="#0000BB"]I[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]J[/COLOR][COLOR="#007700"]:[/COLOR][COLOR="#0000BB"]integer[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]S[/COLOR][COLOR="#007700"]:[/COLOR][COLOR="#0000BB"]String[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]begin
Randomize[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]DigitLst[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Clear[/COLOR][COLOR="#007700"];
for[/COLOR][COLOR="#0000BB"]I[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]1 to StrToInt[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]Edit2[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Text[/COLOR][COLOR="#007700"]) do
[/COLOR][COLOR="#0000BB"]begin
[/COLOR][COLOR="#007700"]for[/COLOR][COLOR="#0000BB"]J[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]1 to StrToInt[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]Edit1[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]text[/COLOR][COLOR="#007700"]) do
[/COLOR][COLOR="#0000BB"]S[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]S[/COLOR][COLOR="#007700"]+[/COLOR][COLOR="#0000BB"]IntToStr[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]Random[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]10[/COLOR][COLOR="#007700"]));
[/COLOR][COLOR="#0000BB"]DigitLst[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Lines[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Add[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]S[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]S[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#DD0000"]''[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]end[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]SL[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]TStringList[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Create[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]SL[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]AddStrings[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]DigitLst[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Lines[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]end[/COLOR][COLOR="#007700"];[/COLOR][/COLOR]
сильно углубляться не стану, скажу лишь, что значение 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 код:
[COLOR="#000000"][COLOR="#0000BB"]unit Unit2[/COLOR][COLOR="#007700"];
interface
[/COLOR][COLOR="#0000BB"]uses
Classes[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]IdHttp[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]type
ParsThrd[/COLOR][COLOR="#007700"]= class([/COLOR][COLOR="#0000BB"]TThread[/COLOR][COLOR="#007700"])
private
[/COLOR][COLOR="#0000BB"]FCurrLnk[/COLOR][COLOR="#007700"]:[/COLOR][COLOR="#0000BB"]String[/COLOR][COLOR="#007700"];[/COLOR][COLOR="#FF8000"]// поле, доступно данному потоку для хранения значения текущего ID
[/COLOR][COLOR="#0000BB"]Ftmp[/COLOR][COLOR="#007700"]:[/COLOR][COLOR="#0000BB"]String[/COLOR][COLOR="#007700"];[/COLOR][COLOR="#FF8000"]//поле для передачи данных VCL объекту
[/COLOR][COLOR="#007700"]protected
[/COLOR][COLOR="#0000BB"]procedure Execute[/COLOR][COLOR="#007700"];[/COLOR][COLOR="#0000BB"]override[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]procedure UpdMemo[/COLOR][COLOR="#007700"];[/COLOR][COLOR="#FF8000"]// метод без параметров для процедуры Synchronize
[/COLOR][COLOR="#0000BB"]end[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]implementation
uses Unit1[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]IdHTTPHeaderInfo[/COLOR][COLOR="#007700"];
var[/COLOR][COLOR="#0000BB"]DoWork[/COLOR][COLOR="#007700"]:[/COLOR][COLOR="#0000BB"]Boolean[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]True[/COLOR][COLOR="#007700"];[/COLOR][COLOR="#FF8000"]// переменная которая оборвет цикл, если в списке с ID закончатся строки.
[/COLOR][COLOR="#0000BB"]procedure ParsThrd[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]UpdMemo[/COLOR][COLOR="#007700"];[/COLOR][COLOR="#FF8000"]// описание метода записывающего результат в мемо формы.
[/COLOR][COLOR="#0000BB"]begin
MainFrm[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]DataLst[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Lines[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Add[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]Ftmp[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]end[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]procedure ParsThrd[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Execute[/COLOR][COLOR="#007700"];
var[/COLOR][COLOR="#0000BB"]Http[/COLOR][COLOR="#007700"]:[/COLOR][COLOR="#0000BB"]TIdhttp[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]ST[/COLOR][COLOR="#007700"]:[/COLOR][COLOR="#0000BB"]TStringStream[/COLOR][COLOR="#007700"];[/COLOR][COLOR="#FF8000"]// Переменная в которую будет писаться ответ сервера, дабы не было проблем с русскими буквами.
[/COLOR][COLOR="#0000BB"]begin
[/COLOR][COLOR="#007700"]while[/COLOR][COLOR="#0000BB"]DoWork[/COLOR][COLOR="#007700"]=[/COLOR][COLOR="#0000BB"]True[/COLOR][COLOR="#007700"]do[/COLOR][COLOR="#FF8000"]//зацикливаем работу потока
[/COLOR][COLOR="#0000BB"]begin
[/COLOR][COLOR="#FF8000"]//CS: TCriticalSection;
//ниже мы используем критическую секцию.
// Код расположен в блоке CS.Enter CS.Leave не будет сеиваться между потоками.
// Здесь мы проверяем на наличие строк в списке, присваиваем значение строки полю FCurrLnk и удаляем эту строку из списка.
// Если не использовать крит. секцию то между потоками возникнет своеобразный хаос,
// с нежелательными последствиями
[/COLOR][COLOR="#0000BB"]CS[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Enter[/COLOR][COLOR="#007700"];
if[/COLOR][COLOR="#0000BB"]SL[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Count[/COLOR][COLOR="#007700"]<>[/COLOR][COLOR="#0000BB"]0
then
begin
[/COLOR][COLOR="#007700"]try
[/COLOR][COLOR="#0000BB"]FCurrLnk[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]Sl[/COLOR][COLOR="#007700"][[/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]];
[/COLOR][COLOR="#0000BB"]SL[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Delete[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]0[/COLOR][COLOR="#007700"]);
finally
[/COLOR][COLOR="#0000BB"]CS[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Leave[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]end
[/COLOR][COLOR="#007700"]else
[/COLOR][COLOR="#0000BB"]begin
CS[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Leave[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]DoWork[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]False[/COLOR][COLOR="#007700"];
Exit;
[/COLOR][COLOR="#0000BB"]end[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#FF8000"]// На данном участке кода формируем более-менее приемлимые заголовки, отсылаем запрос и парсим результат.
[/COLOR][COLOR="#0000BB"]Http[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]TIdHTTP[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Create[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]nil[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]with Http[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Request[/COLOR][COLOR="#007700"]do
[/COLOR][COLOR="#0000BB"]begin
UserAgent[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#DD0000"]'Mozilla/5.0 (Windows NT 5.1; rv:14.0) Gecko/20100101 Firefox/14.0.1'[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]Host[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#DD0000"]'vk.com'[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]AcceptLanguage[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#DD0000"]'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3'[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]ContentType[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#DD0000"]'text/html; charset=windows-1251'[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]end[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]ST[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]TStringStream[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Create[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]''[/COLOR][COLOR="#007700"]);
try
[/COLOR][COLOR="#0000BB"]Http[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Get[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]'http://vk.com/id'[/COLOR][COLOR="#007700"]+[/COLOR][COLOR="#0000BB"]FCurrLnk[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]ST[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]Http[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Disconnect[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]Ftmp[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]ST[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]DataString[/COLOR][COLOR="#007700"];
finally
[/COLOR][COLOR="#0000BB"]http[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Free[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]ST[/COLOR][COLOR="#007700"].[/COLOR][COLOR="#0000BB"]Free
end[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]Ftmp[/COLOR][COLOR="#007700"]:=[/COLOR][COLOR="#0000BB"]Copy[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]Ftmp[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]Pos[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]''[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]Ftmp[/COLOR][COLOR="#007700"]) +[/COLOR][COLOR="#0000BB"]7[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]Pos[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]''[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]Ftmp[/COLOR][COLOR="#007700"]) -[/COLOR][COLOR="#0000BB"]Pos[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#DD0000"]''[/COLOR][COLOR="#007700"],[/COLOR][COLOR="#0000BB"]Ftmp[/COLOR][COLOR="#007700"]) -[/COLOR][COLOR="#0000BB"]7[/COLOR][COLOR="#007700"]);
[/COLOR][COLOR="#0000BB"]Synchronize[/COLOR][COLOR="#007700"]([/COLOR][COLOR="#0000BB"]UpdMemo[/COLOR][COLOR="#007700"]);[/COLOR][COLOR="#FF8000"]// Синхронизируем обращение потока к мемо.
[/COLOR][COLOR="#0000BB"]end[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]end[/COLOR][COLOR="#007700"];
[/COLOR][COLOR="#0000BB"]end[/COLOR][COLOR="#007700"].[/COLOR][/COLOR]
Код постарался привести к максимально читабельному виду. Если что не понятно, спрашивайте, буду рад ответить.
[Эпилог]
Повторюсь еще раз: описанный мною метод не самый оптимальный и взят из личного опыта, но он работает. Буду рад разумной критике и предложениям, если есть что дополнить, не проходите мимо. Софт + исходники созданы исключительно в образовательных целях.
____________
Особая благодарность за консультации и поддержку:
DooD ,
mironich
софт + сурсы