Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Основная";
Текущий архив: 2005.06.29;
Скачать: [xml.tar.bz2];

Вниз

Как избежать гонок в потоках   Найти похожие ветки 

 
leonidus ©   (2005-05-31 10:38) [0]

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


 
Игорь Шевченко ©   (2005-05-31 10:56) [1]

Использовать синхронизацию, например, защищать обращения к массиву критической секцией. Примеры есть в Program Files/Borland/Delphi/Demos/Midas/Pooler/*.*


 
leonidus ©   (2005-05-31 11:08) [2]

Спасибо Игорь, а еще примеры по проще есть, а то там клиент-сервер и мне сложновато зерна от плевел отделить?


 
Digitman ©   (2005-05-31 11:14) [3]


> может так получиться, что они будут обращаться к одному
> элементу массив


синхронизация тебе нужна .. смотри все касаемое критических секций


 
Anatoly Podgoretsky ©   (2005-05-31 11:22) [4]

Может вместо критических секци даже лучше использовать специализирование средство TMultiReadExclusiveWriteSynchronizer


 
Игорь Шевченко ©   (2005-05-31 11:27) [5]

leonidus ©   (31.05.05 11:08) [2]


> а еще примеры по проще есть


Есть. Например, TThreadList в classes.pas


 
leonidus ©   (2005-05-31 15:07) [6]

Понятно...в общем делаю так:

type
...
...

  TThread_work = class(TThread)
  private
    { Private declarations }
    st:string;
  protected
    procedure Execute; override;
    procedure showresult;
  public
  end;

var
 FCS:TCriticalSection;
 t:TThread_work;
 a:array[1..10] of string;
 nom:integer;

implementation

{$R *.dfm}

procedure TThread_work.showresult;
var
i:integer;
begin
for i:=1 to nom do
 begin
  form1.memo1.Lines.Add(a[i]);
 end;
  form1.memo1.Lines.Add("---");

end;

procedure TThread_work.Execute;
begin
FCS.Enter;
inc(nom);
a[nom]:=t.st;

FCS.Leave;
synchronize(showresult);
end;

procedure TForm1.Button1Click(Sender: TObject);
//запускаем потоки
begin
FCS:=TCriticalSection.Create;
nom:=0;

t:=TThread_work.Create(true);
t.FreeOnTerminate:=true;
t.st:="поток1";
t.Resume;

t:=TThread_work.Create(true);
t.FreeOnTerminate:=true;
t.st:="поток2";
t.Resume;
end;

в результате получаются не однозначная картина то

поток1
поток2
---

то вообще:
поток1
---

где грабли?


 
Семен Сорокин ©   (2005-05-31 15:26) [7]


> где грабли?

а вот про это:
synchronize(showresult);
в Execute не забыли?


 
leonidus ©   (2005-05-31 15:31) [8]

а при чем тут synchronize(showresult)?
как будет привильно?


 
Игорь Шевченко ©   (2005-05-31 15:37) [9]


> a[nom]:=t.st;


a[nom] := st;


 
Семен Сорокин ©   (2005-05-31 15:37) [10]


> а при чем тут synchronize(showresult)?
> как будет привильно?

то что глобальные переменные меняются через КС - это нормально, а то что мы в мемо одновременно двумя потоками пишем?


 
Alexander Panov ©   (2005-05-31 15:41) [11]

Семен Сорокин ©   (31.05.05 15:37) [10]
а то что мы в мемо одновременно двумя потоками пишем?


Для того и Synchronize;)


 
Семен Сорокин ©   (2005-05-31 15:45) [12]


> Alexander Panov ©   (31.05.05 15:41) [11]
> Семен Сорокин ©   (31.05.05 15:37) [10]
> а то что мы в мемо одновременно двумя потоками пишем?
>
> Для того и Synchronize;)

все понял :)

>leonidus ©   (31.05.05 15:31) [8]

Признаю, не прав, см.[9].


 
Игорь Шевченко ©   (2005-05-31 16:04) [13]

Alexander Panov ©   (31.05.05 15:41) [11]


> Для того и Synchronize


Нифига подобного. Никто не мешает другому потоку менять содержимое массива во время исполнения метода Syncronize в данном коде.

RTFM.


 
Alexander Panov ©   (2005-05-31 16:23) [14]

Игорь Шевченко ©   (31.05.05 16:04) [13]
Никто не мешает другому потоку менять содержимое массива


Так в примере подразумевается, что доугих потоков нет(кроме основного, конечно).


 
Игорь Шевченко ©   (2005-05-31 16:39) [15]

Alexander Panov ©   (31.05.05 16:23) [14]

В примере два потока, кроме основного.


 
Erik1 ©   (2005-05-31 17:17) [16]

Пример тебе хороший нужен. У меня есть неплохой с использованием TMultiReadExclusiveWriteSynchronizer. Там два списка в потоках обрабатываются, но 11кб фаил занимает, там разумеется дополнительная логика присудствует.


 
leonidus ©   (2005-05-31 21:29) [17]

ок, значит мой пример никик к нормальному виду переделать не удасться?


 
GrayFace ©   (2005-05-31 23:41) [18]

leonidus ©   (31.05.05 21:29) [17]
ок, значит мой пример никик к нормальному виду переделать не удасться?

Да он почти правильный. Ну вот так:
FCS.Enter;
inc(nom);
a[nom]:=t.st;

synchronize(showresult);
FCS.Leave;
end;


Только не вздумай в осн.потоке вызвать WaitForMultipleObjects и т.п. для потоков-обработчиков!

Вообще, CriticalSection работает гораздо быстрее, чем Synchronize. Но для общения с контролами от Synchroniz"а, обычно, никуда не убежать.

PS: Все это ИМХО.


 
Defunct ©   (2005-06-01 00:24) [19]

FCS.Enter;
inc(nom);
a[nom]:=t.st;

synchronize(showresult);
FCS.Leave;
end;

Ненадежно, может подвиснуть всё, из-за любого случайного исключения.


 
Marser ©   (2005-06-01 00:37) [20]


> Вообще, CriticalSection работает гораздо быстрее, чем
> Synchronize. Но для общения с контролами от
> Synchroniz"а, обычно, никуда не убежать.

Вот это оно. TCriticalSection - инкапсуляция очень полезного объекта API. потому и быстрая. И для невизуальных данным прекрасно подходит.
Synchronize реализует тот же принцип исключения многопоточного доступа к данным, но эти данные - объекты VCL, а основная цель - исключение одновременного доступа к DC.

P.S. А ещё есть
InterlockedCompareExchange
InterlockedDecrement
InterlockedExchange
InterlockedExchangeAdd
InterlockedIncrement


 
leonidus ©   (2005-06-01 07:57) [21]

>Defunct что значит "может подвиснуть всё, из-за любого случайного исключения" поясните плиз мысль?


 
evvcom ©   (2005-06-01 08:30) [22]


> leonidus ©   (01.06.05 07:57) [21]

Что у тебя FCS защищает? Массив? Вот и защищай только массив. Используй блокировки как можно на меньшее время. И тем более не переключайся на другой поток, оставив заблокированной критическую секцию. В концовке надо поменять местами строки:

FCS.Leave; // Сначала разблокируй
synchronize(showresult); // а потом в другой поток


 
leonidus ©   (2005-06-01 10:42) [23]

Да защищать нужно будет только запись в массив, правда операция может быть продолжительной т.к. массив 1.000.000 элементов, а что продолжительность работы критической секции столь важно? На другой поток конечно переключаться не буду, пока данный полностью не отработает.
А почему поменять местами? Я сейчас сделал так, и прекрасно работает:

  TThread_work = class(TThread)
  private
    { Private declarations }
    st1:string;
    st2:string;
    st3:string;
  protected
    procedure Execute; override;
    procedure showresult;
  public
  end;

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

implementation

{$R *.dfm}

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

if n=2 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;
inc(nom);
a[nom]:=t.st1;
inc(nom);
a[nom]:=t.st2;
inc(nom);
a[nom]:=t.st3;

synchronize(showresult);
FCS.Leave;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
FCS:=TCriticalSection.Create;

n:=0;
nom:=0;

t:=TThread_work.Create(true);
t.FreeOnTerminate:=true;
t.st1:="поток1: message1";
t.st2:="поток1: message2";
t.st3:="поток1: message3";
t.Resume;

t:=TThread_work.Create(true);
t.FreeOnTerminate:=true;
t.st1:="поток2: message1";
t.st2:="поток2: message2";
t.st3:="поток2: message3";
t.Resume;
end;


 
Игорь Шевченко ©   (2005-06-01 10:43) [24]

leonidus ©   (01.06.05 10:42) [23]

Чукча не читатель ? Чукча write-only ?


> procedure TThread_work.Execute;
> begin
> FCS.Enter;
> inc(nom);
> a[nom]:=t.st1;
> inc(nom);
> a[nom]:=t.st2;
> inc(nom);
> a[nom]:=t.st3;


Убрать нахрен t.


 
GrayFace ©   (2005-06-01 10:48) [25]

evvcom ©   (01.06.05 8:30) [22]</>
См. [13].

Defunct ©   (01.06.05 0:24) [19]
Ненадежно, может подвиснуть всё, из-за любого случайного исключения.

Да, че-то я совсем про try забыл.
FCS.Enter;
try
 inc(nom);
 a[nom]:=t.st;

 synchronize(showresult);
finally
 FCS.Leave;
end;


Еще вариант: всю работу с массивом и Memo поместить в одну процедуру и делать ее Synchronize.


 
КаПиБаРа ©   (2005-06-01 10:48) [26]

Рекомендую почитать
http://mbo88.narod.ru/ToC.html


 
leonidus ©   (2005-06-01 10:55) [27]

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

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;

т.е. synchronizeиспользую до FCS.Leave так?

[13] "Нифига подобного. Никто не мешает другому потоку менять содержимое массива во время исполнения метода Syncronize в данном коде."

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


 
GrayFace ©   (2005-06-01 11:02) [28]

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

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


 
leonidus ©   (2005-06-01 11:05) [29]

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

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-м, не порядок...


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

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:06) [31]

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


 
leonidus ©   (2005-06-01 11:07) [32]

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

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:09) [33]

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

поток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-й поток не на своем месте...


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

leonidus ©   (01.06.05 11:09) [33]

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


 
evvcom ©   (2005-06-01 11:16) [35]


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

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


 
Erik1 ©   (2005-06-01 11:18) [36]

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


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


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


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


 
leonidus ©   (2005-06-01 11:44) [38]

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

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


 
evvcom ©   (2005-06-01 11:52) [39]


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

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

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

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


 
leonidus ©   (2005-06-01 11:56) [40]

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



Страницы: 1 2 3 вся ветка

Форум: "Основная";
Текущий архив: 2005.06.29;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.57 MB
Время: 0.115 c
6-1112392985
Muh
2005-04-02 02:03
2005.06.29
Почему у флешгета получается, а у меня нет :)


5-1088341438
Orik
2004-06-27 17:03
2005.06.29
Как создать кнопку в новом компоненте?...


14-1117698338
lookin
2005-06-02 11:45
2005.06.29
Звук на разных компьтерах


6-1112077504
|)elphin
2005-03-29 10:25
2005.06.29
Работа с Интернетом


3-1116068681
ALex2
2005-05-14 15:04
2005.06.29
изменить значение всех ячеек





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский