Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Как избежать гонок в потоках [D6, WinXP]


Страницы:   1 2 3 4 5

leonidus ©   (2005-05-31 10:38:00) 

Мастера подскажите пожалуйста. Я написал небольшую программу, она скачивает новости с разных сайтов, и затем парсит каждую скачанную страницу на предмет нужной информации. Т.е. программа состоит из бвух блоков - блок закачки (качается сразу в несколько потоков, для каждой страницы с новостями свой поток) и блок парсинга (в нем также предусмотрена многопоточность, при этом для каждой скачанной страницы должен запускаться свой экземпляр одного и тогоже потока). Результаты парсинга (заголовки новостей) помещаются в массив. И вот в чем проблема. Если одновременно скачаются две страницы и одновременно начнут работать два парсера, то может так получиться, что они будут обращаться к одному элементу массив, что бы записать в него полученный заголовок новости. Т.е. я тап понимаю это ситуация гонок. Подскажите как этого избежать. Может есть какие-то статьи по этому поводу, я что-то ничего толком не нашел. Заранее спасибо.


evvcom   (2005-06-01 15:36:00) [41]


> leonidus ©   (01.06.05 15:14) [46]

Какому из потоков выделит квоту времени ОС заранее неизвестно, да это и не важно. Важно только то, что после последнего пишущего в результате synchronize отработает читающий.


leonidus   (2005-06-01 15:14:00) [42]

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


Игорь Шевченко   (2005-06-01 14:50:00) [43]


> Однако в реальной задаче нужно будет читать в произвольный
> момент времени (т.е. конечно можно подождать пока данный
> пишущий поток отработает, но ждать пока отработают все пишущие
> потоки нельзя) и потом запустить процедуру чтения из основного
> потока программы.


Каждый поток при записи выполняет код:

CS.Enter;
try
 inc(nom);
 a[nom] := somedata;
finally
 CS.Leave;
end;

Читающий поток выполняет код:
CS.Enter;
try
 for I:=1 to nom do
   что-то с a[I];
finally
 CS.Leave;
end;

На время выполнения чтения все пишущие потоки блокируются.


evvcom   (2005-06-01 14:02:00) [44]


> Т.е. сначала все потоки поотдельности запишут информацию
> в массив а потом мы из массива организуем чтение.

Написанный выше код, неподразумевает такого. Чтение происходит всякий раз с вызовом showresult


КаПиБаРа   (2005-06-01 13:54:00) [45]

Можно попробовоть так
Читающий поток синхронизируется WaitForSingleObject(MutexRead)
Пишущий поток WaitForMultipleObject(MutexRead и MutexWrite) причем нужно MutexRead освобождать раньше чем MutexWrite


leonidus   (2005-06-01 13:40:00) [46]

Ок, но вот теперь всплыла такая загвоздка. Написанный выше код, подразумевает последовательные операции записи и чтения. Т.е. сначала все потоки поотдельности запишут информацию в массив а потом мы из массива организуем чтение. Однако в реальной задаче нужно будет читать в произвольный момент времени (т.е. конечно можно подождать пока данный пишущий поток отработает, но ждать пока отработают все пишущие потоки нельзя) и потом запустить процедуру чтения из основного потока программы. Как быть в такой ситуации?


evvcom   (2005-06-01 12:02:00) [47]


> но где его взять

В исходниках Borland или их Demo.

> что бы не увязнуть в массе лишнего кода и выделить только
> то, что нужно мне

Надо тренироваться разглядывать именно то, что нужно. IDE Delphi очень удобно для этого сделана. В последствии это умение очень пригодится, когда, устроившись на работу, на тебя упадет задача по доработке чужого и, возможно, далеко не "грамотного" кода.


leonidus   (2005-06-01 11:56:00) [48]

>evvcom про try/finally понял, про грамотный код то же ясно:) но где его взять, что бы не увязнуть в массе лишнего кода и выделить только то, что нужно мне


evvcom   (2005-06-01 11:52:00) [49]


> а почему убрать try/finally, она просто не несет смысловой
> нагрузки или проблема глубже?

try/finally нужна, чтобы гарантировано выполнить код в finally. Для этой конструкции компилятор естественно генерит код, а потом процессор тратит время на выполнение. Если ошибка в try невозможна, то блок этот попусту отжирает ресурсы памяти и времени.

> Но копаться в чужом коде мне кажется еще хуже.

В безграмотном - да, но если код хороший, то очень даже полезно.


leonidus   (2005-06-01 11:44:00) [50]

ок, читаем массив тоже используя критическую секцию:

procedure TThread_work.showresult;
var
i:integer;
begin
inc(n);
if n=kol then
 begin

 FCS.Enter;
 try
  for i:=1 to nom do
   begin
    form1.memo1.Lines.Add(a[i]);
   end;
 finally
  FCS.Leave;
 end;
  form1.memo1.Lines.Add('---');
 end
end;

Т.е. теперь в момент чтений записывающие потоки приостановяться так?

procedure TThread_work.Execute;
//теперь synchronize после критической секции
begin
FCS.Enter;

 inc(nom);
 a[nom]:=st1;
 inc(nom);
 a[nom]:=st2;
 inc(nom);
 a[nom]:=st3;

 FCS.Leave;

synchronize(showresult);

end;

а почему убрать try/finally, она просто не несет смысловой нагрузки или проблема глубже?

Игорь, да я что-то погорячился решил что в какой последовательности запустил в такой они и отработать должны. На самом деле последовательность отработки потоков не важна, важно только что бы потоки паралленьно не писали и чтениие не происходило в параллельно с записью. Я так понял что критически секции в showresult и Execute решат данную проблему.

>Erik1 так то оно так, и пробелы в знаниях есть и механизм потоков знаю слабо, признаю. Но копаться в чужом коде мне кажется еще хуже. Уж лучше свой написать с ошибками который мастера (отдельное спасибо всем) подскажут как подправить, а по ходу обсуждения механизм работы постепенно проясняется.


Игорь Шевченко   (2005-06-01 11:20:00) [51]


> 4-й поток не на своем месте...


Потоки не обязаны выполняться последовательно.


Erik1   (2005-06-01 11:18:00) [52]

Как бы тебе объяснить, у тебя две проблемы. Первая ты плохо представляеш механизм работы потоков и синхронизаторов. Кроме того есть небольшие ошибки в базовых знаниях. Вторя вытекает из первой, ты неможеш предствить как должен работать твой класс, а этого кроме тебя никто незделает. Тут на форуме есть  всего несколько людей которые могут правильно сформулировать тебе твою задачу. Остальные на конкретный вопрос ответят провильно, но ...... Даже для тестовой задачи твой пример будет работать, а в реальных условиях при десятках потоков нет.   Поэтому а и говорил, что тебе следует изучить хороший исходник, а не просто рабочий пример.


evvcom   (2005-06-01 11:16:00) [53]


> GrayFace ©   (01.06.05 10:48) [25]

Ну и нафига здесь synchronize до выхода из крит.секции, да еще try finally навесил? Сейчас посмотрел код showresult, там чтение защищаемого массива. Вопрос в таком случае к тебе, а что из основного потока вызов этой процы, выполняемой по логике в контексте основного потока, уже запрещен? Если нет, то добавить в нее вход/выход из крит.секции, а в Execute убрать нафиг try/finally и выходить из крит.секции доsynchronize! Кроме того, synchronize не гарантирует, что будет запущен немедленно основной поток, может быть запущен другой доп.поток, там выполнена попытка входа в заблокированную крит.секцию и снова переключение потока, хотя фактически в первом доп.потоке запись уже закончена и могла бы быть уже разрешена.


КаПиБаРа   (2005-06-01 11:12:00) [54]

leonidus ©   (01.06.05 11:09) [33]

Почитай по ссылке в КаПиБаРа ©   (01.06.05 10:48) [26].
Твой вопрос можно использовать в качестве контрольного к этому материалу :)


leonidus   (2005-06-01 11:09:00) [55]

в этом случае другая хрень получается на выходе:

поток1: message1
поток1: message1
поток1: message1
поток2: message1
поток2: message1
поток2: message1
поток3: message1
поток3: message1
поток3: message1
поток5: message1
поток5: message1
поток5: message1
поток8: message1
поток8: message1
поток8: message1
поток7: message1
поток7: message1
поток7: message1
поток4: message1
поток4: message1
поток4: message1
поток6: message1
поток6: message1
поток6: message1
поток9: message1
поток9: message1
поток9: message1
поток10: message1
поток10: message1
поток10: message1
---

4-й поток не на своем месте...


leonidus   (2005-06-01 11:07:00) [56]

ок, Игорь, тогда значит так? :

FCS.Enter;
try
 inc(nom);
 a[nom]:=st1;
 inc(nom);
 a[nom]:=st2;
 inc(nom);
 a[nom]:=st3;
finally
 FCS.Leave;
end;

synchronize(showresult);


leonidus   (2005-06-01 11:06:00) [57]

читать массив будет один поток, писать будут много


Игорь Шевченко   (2005-06-01 11:06:00) [58]

leonidus ©   (01.06.05 10:55) [27]


> Ок, тобишь процедура работы с массивом должна выглядеть
> так? :


Нет, не так. Ты входишь в критическую секцию, производишь действия в одном потоке, и передаешь управление в другой поток (через synchronize). При этом все остальные потоки (TThread_work) блокируются нафиг и вся мнопоточность идет лесом.
Защищать критической секцией нужно только обращения к массиву.
CS.Enter;
try
 inc(nom);
 a[nom] := xxx;
finally
 CS.Leave
end;

Там, где читаешь, тоже надо защищать критической секцией чтение, чтобы другие потоки не внесли сумятицу.


leonidus   (2005-06-01 11:05:00) [59]

о, начались сбои при большом кол-ве потоков, учел все выше сказанные замечания, но...

var
 Form1: TForm1;
 FCS:TCriticalSection;
 t:TThread_work;
 a:array[1..100] of string;
 nom,n,kol:integer;

implementation

{$R *.dfm}

procedure TThread_work.showresult;
var
i:integer;
begin
inc(n);

if n=kol then
 begin
  for i:=1 to nom do
   begin
    form1.memo1.Lines.Add(a[i]);
   end;
  form1.memo1.Lines.Add('---');
 end
end;

procedure TThread_work.Execute;
begin
FCS.Enter;
try
 inc(nom);
 a[nom]:=st1;
 inc(nom);
 a[nom]:=st2;
 inc(nom);
 a[nom]:=st3;
 synchronize(showresult);
finally
 FCS.Leave;
end;

end;

procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
begin
FCS:=TCriticalSection.Create;
n:=0;
nom:=0;
for i:=1 to kol*3 do
a[i]:='';

kol:=10; //10 потоков
for i:=1 to kol do
begin
 t:=TThread_work.Create(true);
 t.FreeOnTerminate:=true;
 t.st1:='поток'+inttostr(i)+': message1';
 t.st2:='поток'+inttostr(i)+': message1';
 t.st3:='поток'+inttostr(i)+': message1';
 t.Resume;
end;
end;

и получаю вот что:

поток1: message1
поток1: message1
поток1: message1
поток2: message1
поток2: message1
поток2: message1
поток3: message1
поток3: message1
поток3: message1
поток4: message1
поток4: message1
поток4: message1
поток5: message1
поток5: message1
поток5: message1
поток7: message1
поток7: message1
поток7: message1
поток6: message1
поток6: message1
поток6: message1
поток8: message1
поток8: message1
поток8: message1
поток10: message1
поток10: message1
поток10: message1
поток9: message1
поток9: message1
поток9: message1
---

10-й поток влез перед 9-м, не порядок...


GrayFace   (2005-06-01 11:02:00) [60]

leonidus ©   (01.06.05 10:42) [23]
Да защищать нужно будет только запись в массив

Если у тебя будет много потоков, читающих этот массив, то лучше использовать TMultiReadExclusiveWriteSynchronizer. В приведенном коде это не нужно.


Страницы:   1 2 3 4 5

Наверх

Титульная страница Поиск, карта сайта Написать письмо