Форум: "Начинающим";
Текущий архив: 2008.05.18;
Скачать: [xml.tar.bz2];
Внизпотоки Найти похожие ветки
← →
Аврам (2008-04-17 07:53) [80]Как минимум автор темы, уже этого хватит. Пожалуйста лишнего на себя не берите. Я не претендую на статус мастера, и о каких обидах вы говорите, просто хамить здесь не зачем, я пришел за помощью на форум "Начинающим".
Как вы любите говорить для того чтобы поболтать сущ. другие разделы раз уж на то пошло.
С уважением, А.
← →
sniknik © (2008-04-17 08:51) [81]> Как минимум автор темы, уже этого хватит.
не хватит. автор темы бесполезен, и даже вреден, для обсуждения если он партизан. и может быть даже забанен, пофигу, тема им начатая будет "жить". пока есть интерес других участников.
> я пришел за помощью на форум "Начинающим".
имхо конечно, но как раз помощь тебя и не интересует, ты просто любишь "парить" людям мозг...
помочь сделать чтото, не зная что, невозможно. а смысл таинственной обработки данных до сих пор не раскрыт.
> Как вы любите говорить для того чтобы поболтать сущ. другие разделы раз уж на то пошло.
видиш ли, твоя ветка настолько не изобилует подробностями, бедна информацией о собственно задаче (метод решения -> перенос в потоки, задачей не является), что попросту провоцирует треп на эту тему. и давно уже она кандидат на перенос в "другие разделы раз уж на то пошло" конкретно в потрепаться. нехватает малости (возможно твоего ответа на вот этот пост, в практикуемом тобой стиле, опять без подробностей что делается).
← →
ЦУП © (2008-04-17 09:35) [82]
> Аврам (17.04.08 01:11) [78]
А скажи, это принципиально - отображение на форме такой кучи данных?
Если нет, то ты уже сам озвучивал, чт оможно использовать TStringList.
А ещё лучше - связный список. Накладных расходов будет меньше.
← →
Palladin © (2008-04-17 09:49) [83]Итак, начем"с разбор полетов :)
сначала
> Германн © (17.04.08 01:02) [77]
> sniknik © (17.04.08 00:30) [76]
понимаите, понимашь, ну вот возьмем конструкцию array of, зачем ее сделали? нафик она нужена то? Николай предлагает универсальное придуманное решение TDataSet. Давным давно уже все придумали. И индексацию, и фильтрацию.
Возьмем TList, да вот тоже непонятные аффтары Делфи, зачем нужен этот TList, когда есть универсальное придуманное решение TDataSet. Давным давно уже все придумали. И индексацию, и фильтрацию.
Возьмем TStringList. Это вообще не понятно, конечно же для хранения строк лучше использовать универсальное придуманное решение TDataSet. Давным давно уже все придумали. И индексацию, и фильтрацию.
Так вот, объясняю. Как выглядят данные аффтора и что он пытается организовать. Есть некий параметр P, для него есть набор однотипных данных для расчета, аффтар не желает обрабатывать их последовательно, я считал это нежелание абсолютно необоснованным до [30] и [50]го поста. Но в свете [30 и [50], мысль на счет распараллеливания очень даже имеет право на существование, более того это самый оптимальный вариант при таких условиях. Теперь вернемся к хранению данных. Как их следует хранить? Если я прав на счет структуры, а пока я не вижу никаких противоречий, то расскажу я аффтору, на основе его же рисунков, как же это сделать правильно.
Для начала надо продумать, как мы будем их отображать и в каком виде, есть два варианта. Плоский табличный либо небольшое двууровневое дерево
P -> Params для P. Бо от этого зависит как проще построить запись описания данных. В любом случае это должен быть либо TListView, либо Virtual TreeView (http://www.soft-gems.net/), бо первый имеет виртуальный режим работы, а второй по другому просто не умеет :).
Почему так важна виртуальность. Виртуальность позволит нам хранить записи в своих структурах не храня их в самих визуальных компонентах, они (визуальные компоненты) лишь обращаются к нам если им понадобится какое либо значение.
← →
Palladin © (2008-04-17 09:49) [84]Путь TListView"а
Если мы идем по пути TListView, то для простоты кодирования нужно будет слегка пожертвовать памятью, но это совсем некритично, в свете примерных количественных данных, тобою описанных.
Описание данных будет иметь такой вид.
Type
PLVDataRec=^TLVDataRec;
TLVDataRec=Record
P:Integer; // наш любимый P
Params:Array of Integer; // наши параметры, их может быть разное количество
... // какие нибудь еще данные связанные с P
End;
Var
theData:TList; // основной источник данных
Этот каноническое решение для работы со списками. Но еще немножко подумав, мы решаем, а зачем нам ненужный функционал TList, нам всего то нужно заполнить массив и последовательно удалять отработанные элементы. И приходим к такому решению, что еще более оптимальней.
Type
TLVDataRec=Record
P:Integer; // наш любимый P
Params:Array of Integer; // наши параметры, их может быть разное количество
... // какие нибудь еще данные связанные с P
End;
Var
a:Array of TLVDataRec; // основной источник данных, с обратным заполнением
Обратное заполнение означает - первый элемент является последним в массиве. И идти по массиву мы будем задом наперед. Почему так? А потому что не нужно будет заморачиватся на удалении отработанных элементов. SetLength и все.
Идем дальше. Организовать отображение данных в TListView (вид vsReport) это не проблемма. Оставлю аффтору на домашнее задание. Информации про его виртуальный режим в интернете выше крыши. Нас сейчас интересует как поменяется работа, вышенаписанной мной, схемы монитора и потоков. А сильно она и не поменяется. Лишь в области передачи данных, очереди посылки записи на обработку и обновления вида TListView.Type
TLVDataRec=Record
P:Integer; // наш любимый P
Params:Array of Single; // наши параметры, их может быть разное количество
// какие нибудь еще данные связанные с P
End;
TDataArray=Array of TLVDataRec;
Var
a:TDataArray; // основной источник данных, с обратным заполнением
Type
TWorkThread=Class(TThread)
Private
m_theEndFlagP:TMultiReadExclusiveWriteSynchronizer;
m_bEndFlag:Boolean;
m_pResultData:Pointer; // переименовал дабы небыло несуразностей
m_prData:^TLVDataRec; // незачем копировать запись, работаем по ее адресу
Function lcGetEndFlag:Boolean;
Protected
Procedure Execute; Override;
Public
Constructor Create(Const p_rData:TLVDataRec);
Destructor Destroy; Override;
Property EndFlag:Boolean Read lcGetEndFlag;
Property Data:Pointer Read m_pResultData;
End;
TMonitorThread=Class(TThread)
Private
m_aDataRef:^TDataArray;// ссылка на исходный массив, именно ссылка иначе будет создана копия
m_theListView:TListView; // трудяга визуализатор
m_nDropLinesFrom:Integer; // мы работаем теперь с конца массива
Procedure DropLines;
Protected
Procedure Execute; Override;
Public
Constructor Create(Const p_aData:TDataArray;p_theListView:TListView);
End;
Constructor TWorkThread.Create;
Begin
m_theEndFlagP:=TMultiReadExclusiveWriteSynchronizer.Create;
m_prData:=@p_rData;
m_bEndFlag:=False;
Inherited Create(False);
End;
Destructor TWorkThread.Destroy;
Begin
Inherited;
m_theEndFlagP.Free;
End;
Procedure TWorkThread.Execute;
Var
i:Integer;
Begin
// берем ресурсы
Try
For i:=0 to Random(10*1000)-1 Do
Begin
Sleep(100); // создаем видимость работы в поте интерфейса
If Terminated Then Exit;
End;
m_theEndFlagP.BeginWrite;
Try
m_bEndFlag:=True;
Finally
m_theEndFlagP.EndWrite;
End;
Finally
// освобождаем ресурсы
End;
End;
Function TWorkThread.lcGetEndFlag;
Begin
m_theEndFlagP.BeginRead;
Try
Result:=m_bEndFlag;
Finally
m_theEndFlagP.EndRead;
End;
End;
Procedure TMonitorThread.Execute;
Var
theListOfThreads:TList;
Procedure _FreeThreadsList;
Var
i:Integer;
Begin
For i:=0 to theListOfThreads.Count-1 Do TThread(theListOfThreads).Free;
theListOfThreads.Clear;
End;
Function _CreateThreads:Boolean;
Var
n,P:Integer;
Begin
n:=Length(m_aDataRef^)-1;
Result:=n<>-1;
If Not Result Then Exit;
P:=m_aDataRef^[n].P;
While True Do
Begin
Result:=Not Terminated;
If Not Result Then Exit;
theListOfThreads.Add(TWorkThread.Create(m_aDataRef^[n])); Dec(n);
If n=-1 Then Begin m_nDropLinesFrom:=n+1; Exit; End Else
If m_aDataRef^[n].P<>P Then Begin m_nDropLinesFrom:=n+1; Exit; End;
End;
End;
Function _WaitForThreads:Boolean;
Var
i:Integer;
Begin
i:=0;
While True Do
Begin
Result:=Terminated;
If Result Then Exit;
If TWorkThread(theListOfThreads[i]).EndFlag Then
Begin
ChegoNibudDelaemS(TWorkThread(theListOfThreads[i]).Data);
Exit;
End;
Inc(i); If i=theListOfThreads.Count Then i:=0;
Sleep(100);
End;
End;
Begin
theListOfThreads:=TList.Create;
Try
While True Do
Begin
If Terminated Then Exit;
m_nDropLinesFrom:=-1;
If Not _CreateThreads Then Exit;
If _WaitForThreads Then Exit;
Synchronize(DropLines);
_FreeThreadsList;
End;
Finally
_FreeThreadsList;
theListOfThreads.Free;
End;
End;
Constructor TMonitorThread.Create;
Begin
m_aDataRef:=@p_aData;
m_theListView:=p_theListView;
Inherited Create(False);
End;
Procedure TMonitorThread.DropLines;
Begin
If m_nDropLinesFrom=-1 Then Exit;
SetLength(m_aDataRef^,m_nDropLinesFrom);
m_theListView.Items.Count:=Length(m_aDataRef^);
m_theListView.Refresh;
End;
вуаля...
и еще скажу на последок поклонникам БД: TDataSet идет лесом, в случае четкой структуры данных предназначенных для обработки и ни для чего более. Идеология TDataSet - отображение (не визуальное имеется ввиду, а в контексте делфи отображение) набора записей БД и предоставление гибкого (в плане зависимости от БД) основного функционала работы с ним. Вот решит аффтар или его шеф(ы), сделать входящий массив данных устойчивым и хранящемся в БД, тогда будет саааавсем другой разговор.
Путь Virtual TreeView, будет описан вечером, бо мне и свою работу работать надо :)
← →
Anatoly Podgoretsky © (2008-04-17 10:05) [85]> sniknik (17.04.2008 08:51:21) [81]
По уму и обсуждение.
← →
sniknik © (2008-04-17 11:31) [86]> сделать входящий массив данных устойчивым и хранящемся в БД, тогда будет саааавсем другой разговор.
саааавсем необязательно хранить данные в БД, чтобы использовать механизмы обработки данных используемые в БД если они вдруг подходят для задачи.
БД то как раз никто и не предлагал.
← →
ANB (2008-04-17 12:29) [87]
> обоснуй. я вот обращаюсь...
Можно и обращаться. Но если это делать корректно, то потоки будут ждать друг друга.
← →
ANB (2008-04-17 12:36) [88]
> БД то как раз никто и не предлагал.
Я предлагал. У меня подозрение, что БД выполнит алгоритм обработки намного шустрее и распараллелка просто не понадобится.
← →
Palladin © (2008-04-17 12:39) [89]
> ANB (17.04.08 12:29) [87]
Очень редкий случай, когда при распараллеливании не нужна синхронизация, а в случае синхронизации - всегда может возникнуть ситуация, когда один поток должен подождать пока не отработает какой то другой. Так что это в порядке вещей. И так категорически заявлять о запрете на обращение к VCL в потоках не нужно. Никак производительность, нормально спроектированной, многопоточной системы не будет страдать от этого.
> sniknik © (17.04.08 11:31) [86]
ну да... семейство inmemory dataset"ов никто не отменял...
но, как ты верно отметил по поводу "подходят для задачи", если собаке для выполнения, ее собачьих функций, понадобилась бы пятая нога я думаю она у нее была бы. а так, ну можно и пришить хирургически, или искусственную присобачить... :) а смысл?
← →
ANB (2008-04-17 12:47) [90]
> а смысл?
Смысл - не в увлеченном программировании потоков, содержащем достаточно много подводных камней, а в выборе оптимального метода решения задачи.
> Никак производительность, нормально спроектированной, многопоточной
> системы не будет страдать от этого.
Выделенное является главным.
Я не писал о полном запрете на работу с VCL из потоков. И если меня поняли не так - уточнил это в следующем посте.
Никто не запрещает сложить все данные для расчета в визуальный компонент и лезть через синхронизе к нему из тысячи потоков. Только быстрее чем один они скорее всего работать не будут.
← →
sniknik © (2008-04-17 12:52) [91]> а смысл?
имхо достаточный смысл, если там (что упоминалось) есть частый поиск значений одних данных в других.
← →
Palladin © (2008-04-17 13:00) [92]
> sniknik © (17.04.08 12:52) [91]
перечитал [34] никаких намеков на поиск значений среди других не увидел...
> ANB (17.04.08 12:47) [90]
а ты читал [30]? вдумчиво читал?
← →
Dennis I. Komarov © (2008-04-17 13:05) [93]> а в выборе оптимального метода решения задачи
А вот этого мы так и не узнаем, т.к. [38] и [51]
← →
Palladin © (2008-04-17 13:09) [94]
> Dennis I. Komarov © (17.04.08 13:05) [93]
и ты [30] не понял... там есть ключевая фраза, которая и делает распаралелливание оптимальным решением...
← →
sniknik © (2008-04-17 13:11) [95]> Я предлагал. У меня подозрение, что БД выполнит алгоритм обработки намного шустрее и распараллелка просто не понадобится.
про нужность "распараллелки" согласен, единственное БД это всетаки дополнительные заботы. и если можно обойтись без нее, я бы постарался обойтись.
а каким образом оптимизировать обработку с помощью датасета или динамического массива под TreeView в принципе не суть важно. датасет просто потребует меньше усилий при больших возможностях.
> перечитал [34] никаких намеков на поиск значений среди других не увидел...
> обычная процедура как я делал с самого начала без потоков брала первую строчку из списка и пускала в процедуре анализ этой строки.
> Переменная count как раз говорит сколько раз надо проверить каждую строчку.
> Т.е. 100 раз первую сто раз вторую.
> типа for i:=0 to count -1 do begin
> Если найден результат по заданым мной критериям и i= 19 то в поле NY мы добавляем результат например 0,0102
> и переходим к анализу строчки с id =2 и т.д.
думаешь 100 раз проверка идет одной и той же строки? так сказать для гарантии результата?
p.s. бред это все, без четкого описания "обработки" автором. посему надо закругляться.
← →
Dennis I. Komarov © (2008-04-17 13:17) [96]> [94] Palladin © (17.04.08 13:09)
ну возможно...
НО
> p.s. бред это все, без четкого описания "обработки" автором.
> посему надо закругляться.
← →
Palladin © (2008-04-17 13:17) [97]
> Т.е. 100 раз первую сто раз вторую.
> >типа for i:=0 to count -1 do begin
я вижу это так: у него просто два списка один как в [34], первого уровня, второй другой как [8] по P (id)
> думаешь 100 раз проверка идет одной и той же строки? так
> сказать для гарантии результата?
нет, у него просто для одного P (id) - 100 значений Z по которым он считает, расчет многих Z может разультатов не дать (см [30])... а ему нужен первый получившийся результат... потому многопоточность будет оптимальней последовательности.
> без четкого описания "обработки" автором
ладно, согласен, ждем аффтора... весна покажет кто где гадил :)
← →
ANB (2008-04-17 13:20) [98]
> которая и делает распаралелливание оптимальным решением.
> ..
Распараллеливание на клиенте с хранением всех данных в стриглисте ?
С залезанием в него на каждый чих ?
Для сведения - оракл прекрасно умеет получать данные по HTTP протоколу.
А подводных камней при распараллеливании в нем намного меньше.
← →
Palladin © (2008-04-17 13:26) [99]
> Распараллеливание на клиенте с хранением всех данных в стриглисте
> ?
> С залезанием в него на каждый чих ?
читать два последних абзаца [83] и мое ессе путь TListView"а до просветления... StringGrid уже давно не рассматривается...
← →
ANB (2008-04-17 13:55) [100]
> Palladin © (17.04.08 13:26) [99]
>
> > Распараллеливание на клиенте с хранением всех данных в
> стриглисте
> > ?
> > С залезанием в него на каждый чих ?
>
> читать два последних абзаца [83] и мое ессе путь TListView"а
> до просветления... StringGrid уже давно не рассматривается.
> ..
Обработка 10-20 тысяч записей более 5 минут - это значит проблемы в генофонде. А если укладываться в эти рамки - зачем вообще нужна пользователю визуализация процесса ?
← →
Аврам (2008-04-17 16:40) [101]афтар тут.
да, больше времени занимает загрузка страницы с интернета, парсинг и анализ проходят быстро....млин все упирается в долгую скачку исходного текта страницы, прост опри разных параметрах запросы получаюстя разные, отсюда и разного размера страница для получения данных с сайта.
← →
Аврам (2008-04-17 16:41) [102]к сожалению понял это только сейчас...
← →
Palladin © (2008-04-17 16:42) [103]
> да, больше времени занимает загрузка страницы с интернета,
лично я в этом и не сомневался....
← →
sniknik © (2008-04-17 16:48) [104]> больше времени занимает загрузка страницы с интернета
вот против выноса загрузки с с интернета в отдельный поток думаю никто возражать не будет.
но ты то делаешь совсем другое, чтото что может вообще времени не занимает "распараллеливать" (да так что оно таки время занимать начинает).
почему и нужно четкое описание, что же всетаки делается.
← →
Anatoly Podgoretsky © (2008-04-17 16:55) [105]> Аврам (17.04.2008 16:40:41) [101]
Вот и Интернет появился, но это хоть какое то разумное объяснения применения потоков.
← →
ANB (2008-04-17 17:02) [106]
> Аврам (17.04.08 16:40) [101]
> афтар тут.
> да, больше времени занимает загрузка страницы с интернета,
> парсинг и анализ проходят быстро....млин все упирается
> в долгую скачку исходного текта страницы, прост опри разных
> параметрах запросы получаюстя разные, отсюда и разного размера
> страница для получения данных с сайта.
Есть смысл выкачать все, что может понадобиться, а уже на локале разбить на кусочки.
Мона, конечно, распараллелить выкачку с разными параметрами, но что то мне подсказывает, что загрузить одну большую страничку в одном потоке получится быстрее, чем даже в распараллелке выкачать кучу маленьких. Хотя, возможно, я и не прав. Шустрые качалки этот процесс распараллеливают.
← →
Palladin © (2008-04-17 17:06) [107]
> Anatoly Podgoretsky © (17.04.08 16:55) [105]
с добрым утром... интернет давно появился, я уже запарился всем на это указывать...
← →
sniknik © (2008-04-17 17:11) [108]> Шустрые качалки этот процесс распараллеливают.
они это делают чтобы обойти ограничение сервера на скорость в одной сессии. т.е. если на сервере ограничивают скачку 100кб-тами, но поддерживают закачку частями, а у вас инет на 1мб, то понятно 10 потоков (до упора своего канала) со скоростью 100кб выкачают файл быстрее чем один на 100.
но вот если переборщить и качать например в 1000 потоков то это уже скорости не прибавит, наоборот понизит, + куча лишнего служебного трафика отожрет.
← →
Palladin © (2008-04-17 17:16) [109]о... люди просыпаться начали... вкуривать в ветку... о количестве потоков я осведомился еще в [43] и [44] и не стоит думать что соединение до источника данных идеально... так что понизит или нет еще вопрос, траффик, да, скушает, но если грамотно подойти к вопросу получения информации из интернета, не используя компоненты высокого уровня, а работая на низком уровне сокетов, то можно рвать связь при необходимости...
← →
sniknik © (2008-04-17 17:17) [110]> интернет давно появился, я уже запарился всем на это указывать...
его никто (из иначальных в этой ветке) и не игнорировал, но обсуждалось то не помещение части с инетом в поток, а распараллеливание части с "таинственной обработкой данных", которая по логике уже после/вместо/до/пофигу в общем когда но точно <> "закачке данных из инета".
← →
ANB (2008-04-17 17:37) [111]
> Palladin © (17.04.08 17:16) [109]
> о... люди просыпаться начали... вкуривать в ветку... о количестве
> потоков я осведомился еще в [43] и [44] и не стоит думать
> что соединение до источника данных идеально... так что понизит
> или нет еще вопрос, траффик, да, скушает, но если грамотно
> подойти к вопросу получения информации из интернета, не
> используя компоненты высокого уровня, а работая на низком
> уровне сокетов, то можно рвать связь при необходимости..
> .
Боюсь, если автор начнет это реализовывать, то он из форума не вылезет.
Повышение скорости закачки из инета - это отдельная песня.
ЗЫ. Не думаю, что серверу, с которого качают данные, понравится 10 000 одновременных запросов с одного IP.
← →
Palladin © (2008-04-17 17:39) [112]
> ANB (17.04.08 17:37) [111]
и где ты взял столько много? процитировать меня процитировал, а вот на ссылаемые посты взглянуть не удосужился... ай ай ай...
← →
Аврам (2008-04-17 18:17) [113]во блин мужики, я сам офигел. вообще я доделываю начатую работу, только тот программер свалил. Я только щас выяснил что кол-во данных загруженных со страницы - разное при разных запросах к сайту.
Когда обгаваривалось это вначале с заказчиком, то он посчитал что это не имеет значения. чисто случайно проверил. млин!
я и думаю как то странно все получается. оказывается вся загвоздка в этих обращениях к сайту.
получается что надо выносить скачку страницы с сайта в эти отдельные потоки чтоли...
← →
ANB (2008-04-17 18:29) [114]
> Аврам (17.04.08 18:17) [113]
> во блин мужики, я сам офигел. вообще я доделываю начатую
> работу, только тот программер свалил. Я только щас выяснил
> что кол-во данных загруженных со страницы - разное при разных
> запросах к сайту.
> Когда обгаваривалось это вначале с заказчиком, то он посчитал
> что это не имеет значения. чисто случайно проверил. млин!
>
> я и думаю как то странно все получается. оказывается вся
> загвоздка в этих обращениях к сайту.
> получается что надо выносить скачку страницы с сайта в эти
> отдельные потоки чтоли...
Я бы самым первым попытался сделать запрос или группу их, чтобы минимизировать количество обращений к серверу.
Т.е. одним или несколькими запросами выкачать все на локал, а потом обрабатывать.
И только если время предварительной выкачки все равно будет занимать слишком много времени, начать работу по ускорению этого процесса. В том числе и за счет распараллеливания.
А гос. Palladin вам в этом поможет.
← →
Palladin © (2008-04-17 18:33) [115]Я? Я умываю руки и помогать уже не буду. Всем чем мог помог. Написал много букафф. Дело аффтара их переварить или выбросить в помойку.
← →
Аврам (2008-04-17 18:36) [116]я так понимаю что будет самое то если вынести закачку странциы по запросу.
т.е. выглядеть будет так
типа page :=get(*******);
if posex(Param_result, page, 1) <> 0 then
begin
....парсинг результатов
в listview ставиться значения result
все остальные потоки вырубаются
end;
← →
Palladin © (2008-04-17 18:37) [117]Да, кстати относительно визуализации процесса. Его вообще можно не визуализировать. Я, при написании примера распараллеливания, не ставил это главной задачей, как можно убедится глянув исходники, отказ от визуализации делается очень просто, выбрасыванием к чертям этого Synchronize и переносе исполнения функционала удаления отработанных данных в поток монитор. Только некоторые заказчики любят когда у них там строчечки появляются и исчезают :) типа "компьютер думает"...
← →
Palladin © (2008-04-17 18:40) [118]
> Аврам (17.04.08 18:36) [116]
это не поможет, get будет висеть пока не получит все данные по запросу. работать нужно с сокетами на низком уровне, дабы иметь возможность по требованию монитора закрыть соединение и завершится. а здесь я тебе уже не помошник. проси слезно Сергея М., но он не будет таким "лапочкой" как я :)
← →
Аврам (2008-04-17 18:46) [119]спасибо большое всем за ответы и советы, особенные благодарности Palladin.
могли бы вы мне посоветовать какие нить книги или матерьялы по теме потоков. хотелось бы основательно в этом разобраться чтобы понимать все как вы)
← →
Anatoly Podgoretsky © (2008-04-17 18:51) [120]> Palladin (17.04.2008 18:37:57) [117]
Заказчика надо любить, я иногда даже грид не отрубаю при проходе по набору данных, пользователь видит, что идет работа, ему приятно, а для меня недорого.
Страницы: 1 2 3 4 вся ветка
Форум: "Начинающим";
Текущий архив: 2008.05.18;
Скачать: [xml.tar.bz2];
Память: 0.73 MB
Время: 0.052 c