[Предисловие] В своей практике частенько встречаюсь с необходимостью написания многопоточных приложений. Вот решил поделиться опытом и показать основные краеугольные камни синхронизации потов. Было у меня время, когда при упоминании многопоточности я обливался холодным потом, а мое анальное отверстие сжималось с такой силой, что могло спокойно перекусить металлический лом, или кусок арматуры. У большинства начинающих кодеров, которые впервые слышат о потоках ощущения примерно те-же. Вот для вас господа я то и стараюсь . Предупреждаю сразу: я не експерт и не гуру кодинга, все что буду здесь писать основано на личном опыте. Если будут критические замечания или дельные советы, судовольствием добавлю их в сей текст. [Нах оно нужно?] Вообще-то статейка направлена на кодеров, которые уже в курсе что такое потоки и как создавать поток. Но все-же скажу пару мотивирующих слов... Основное преимущество многопоточного приложения - это скорость выполнения работы. Я преимущественно имею дело с приложениями, которые работают через вэб интерфейсы и по сравнению с однопоточными, приложения работающие в несколько потоков по скорости выигрывают в разы. Ну воды уже налил достаточно, подошло дело к практике . [Кодинг] ТЗ: Создать многопоточный парсер имени и фамилии социальной сети ВКонтакте по списку ID. Своей извращенной фантазией я представил данное приложение в таком виде: 1 - Поле мемо (DigitLst) со списком чисел ID. 2 - Поле мемо (DataLst) со списком спаршенных имен. 3 - Для генерации чисел набросал простенькую процедурку: PHP: procedure TMainFrm.Button1Click(Sender: TObject); var I,J: integer; S: String; begin Randomize; DigitLst.Clear; for I := 1 to StrToInt(Edit2.Text) do begin for J := 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, аргументом которой будет процедура обращения к визуалу без параметров. PHP: 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; var DoWork: Boolean = True; // переменная которая оборвет цикл, если в списке с ID закончатся строки. procedure ParsThrd.UpdMemo; // описание метода записывающего результат в мемо формы. begin MainFrm.DataLst.Lines.Add(Ftmp); end; procedure ParsThrd.Execute; var Http: TIdhttp; ST: TStringStream; // Переменная в которую будет писаться ответ сервера, дабы не было проблем с русскими буквами. 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) -7 ); Synchronize(UpdMemo); // Синхронизируем обращение потока к мемо. end; end; end. Код постарался привести к максимально читабельному виду. Если что не понятно, спрашивайте, буду рад ответить. [Эпилог] Повторюсь еще раз: описанный мною метод не самый оптимальный и взят из личного опыта, но он работает. Буду рад разумной критике и предложениям, если есть что дополнить, не проходите мимо. Софт + исходники созданы исключительно в образовательных целях. ____________ Особая благодарность за консультации и поддержку: DooD , mironich софт + сурсы
Лучше раз и в конструкторе, чем каждый раз создавать. А так я рад что ты таки написал ее. ЗЫ есче всегда выходить из крит секции надо в try: finally: иначе искл. поймаем и не выйдем.