Главная страница
    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 понял, про грамотный код то же ясно:) но где его взять, что бы не увязнуть в массе лишнего кода и выделить только то, что нужно мне


 
evvcom ©   (2005-06-01 12:02) [41]


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

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

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

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


 
leonidus ©   (2005-06-01 13:40) [42]

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


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

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


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


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

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


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


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


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

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;

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


 
leonidus ©   (2005-06-01 15:14) [46]

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


 
evvcom ©   (2005-06-01 15:36) [47]


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

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


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


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


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


 
leonidus ©   (2005-06-01 15:50) [49]

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

procedure TThread_work.showresult;
var
i:integer;
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;


 
leonidus ©   (2005-06-01 15:55) [50]

Игорь, вы предлагаете динамично менять приоритеты потоков, но ведь можно обойтись и без этого, я думал что код в [49] решит такую задачу. А на счет что главнее, мне как раз важнее что бы у меня была возможность в любой момент считать данные из массива, т.к. эти данные (по сути URL-ссылки) подаются на вход блока скачивания (см. [1]) а результаты парсинга (новые URL ссылки) записываются в массив.


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

По моему ты этот код именно в таком виде уже приводил. Да типа того, правильно. Только надо еще добавить form1.memo1.Lines.Clear; а то увидишь несколько копий того, что хотел увидеть с "нарастающим итогом".
Недостаток. Увидишь Memo, мерцающий столько раз сколько раз он будет вызван. Чтобы этого избежать, не делать Clear и добавлять в Memo только добавленный в текущем потоке a[i]


 
Alexander Panov ©   (2005-06-01 16:06) [52]

leonidus ©   (01.06.05 15:55) [50]
мне как раз важнее что бы у меня была возможность в любой момент считать данные из массива


Для этого используется флажок:

- Читающий поток взводит флаг.
- Пишущий поток проверяет флаг, и если он взведен - поток ничего не пишет и не входит в критическую секцию.

либо(как выше было написано) у читающего потока нужно повысить приоритет.


 
evvcom ©   (2005-06-01 16:22) [53]


> Для этого используется флажок:

А это еще зачем? Сама критическая секция уже и есть нужный "флажок".

> мне как раз важнее что бы у меня была возможность в любой
> момент считать данные из массива

Да нормально ты все считаешь. Вход в крит.секцию, запись в массив, выход из секции займет миллионные, если не меньше, доли секунды, ты даже ничего и не заметишь.


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


> нужно после отработки каждого потока вызывать synchronize
> который по сути передает управление главному потоку и если
>  главному потокe к данному моменту потребовалось считать
> данные из массива


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


 
Alexander Panov ©   (2005-06-01 17:45) [55]

evvcom ©   (01.06.05 16:22) [53]
А это еще зачем? Сама критическая секция уже и есть нужный "флажок".


Нет, н флажок.
Критическую секцию равноправно могут использовать как пишущий, так и читающий потоки.


 
leonidus ©   (2005-06-01 19:35) [56]

>Игорь я это вижу так: пишущие потоки стоят в очереди и по одному в соответствии с очередью входят в критическую секцию и записывают в массив свою порцию данных. Закончив запись управление через synchronize передается главному потоку программы который определяет нужно ему читать или нет (т.е. если считанная до этого порция ссылок не скачалась за новой не идем, если скачалась, нужно обратиться к массиву за новой порцией ссылок). В случае если ничего читать не надо процедура которую мы вызвали из synchronize завершается и массив отдается на запись записывающим потокам, если же из массива что-то нужно считать, чтение производится через критическую секцию, и только после этого процедура завершается передавая доступ к массиву пишущим потокам. И тут сразу вопрос, если ничего читать не надо, что достаточно просто скачем через goto перейти в конец процедуры и записывающая очередь сама получит управления и начнет работать или приостановленную очередь надо как-то специально запустить на продолжение? или я все усложняю?


 
leonidus ©   (2005-06-01 19:42) [57]

А на счет флагов мне то же не ясно. Флаг я нужен для сигнализации между пищущими и читающими потоками. Но если пищущий или читающий поток уже вошел в критическую секцию, другой просто в нее войти не сможет, выходит тут уже как бы и так передается сигнал занятости.

Alexander Panov ©   (01.06.05 17:45) [55]
Критическую секцию равноправно могут использовать как пишущий, так и читающий потоки.


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


 
Polevi ©   (2005-06-01 20:17) [58]

>записывающая очередь сама получит управления и начнет работать или приостановленную очередь надо как-то специально запустить на продолжение

получит управление один из потоков ожидающих освобождения критической секции после после того как отработавший поток вызовет LeaveCritivalSection


 
Alexander Panov ©   (2005-06-01 21:01) [59]

leonidus ©   (01.06.05 19:42) [57]
...Первый будет записывающий поток, после его завершения передаем управление главному...


Тебе ведь нужно, чтобы приоритетным был читающий поток?

В случае с равноправными потоками(а основной поток тоже является таковым - пусть он будет читающим - R) R при необходимости чтения данных будет пытаться войти в критическую секцию. Если в это время то же самое пытаются сделать остальные пишущие потоки(W), то начать выполняться может любой поток.
Для гарантированного получения времени процессора по требованию у R либо должен быть выше приоритет, либо(как я писал выше), необходимо, чтобы R устанавливал флажок "Читаю", который W должны проверять.
W проверяет флажок. Если флажок установлен в "Читаю", W не пытается входить в критическую секцию, а переходит в состояние ожидания, пока флажок не будет сброшен, соответственно W и не записывает данные в это время.

Но R после установки флажка "Читаю" должен также дождаться, пока текущий W закончит записывать данные, если таковой существует, т.е. для этого необходим тоже некий флаг.

Для таких флагов подходят как семафоры, так и мьютексы.


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

А количество пишущих потоков фиксировано или их может быть произвольное число ? И от чего это зависит ?


 
leonidus ©   (2005-06-01 22:41) [61]

>Alexander Panov ©
Ок теперь я понял для чего флаги. Дело в том, что мне бы хотелось всю черную работу возложить на ОС, она лучше меня справится с разделением квантов процессорного времени:) Наверное что бы не запутаться с флагами поставлю разные приориты у читающего и пищущего потоков. А достаточно сделать различия в один уровень или надо больше?

>Игорь Шевченко ©
Кол-во пишущих потоков может быть от одного до думаю нескольких десятков. А зависит это от того сколькими потоками будет вестись скачивание документов, на каждый скачиваемый документ свой парсер результаты работы которого (новые ссылки) будут писатся в массив.

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


 
Alexander Panov ©   (2005-06-01 23:09) [62]

leonidus ©   (01.06.05 22:41) [61]
Наверное что бы не запутаться с флагами поставлю разные приориты у читающего и пищущего потоков. А достаточно сделать различия в один уровень или надо больше?


При повышении приоритета учти, что у тебя есть еще и другие процессы в системе.


 
Alexander Panov ©   (2005-06-01 23:10) [63]

На память не помню, имеет ли основной поток равные приоритеты с дополнительными.


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

leonidus ©   (01.06.05 22:41) [61]


> А достаточно сделать различия в один уровень или надо больше?


Достаточно. Причем, поток, работа которого важна, должен работать с нормальным приоритетом, а не очень важные потоки - с пониженным, чтобы не мешать потокам остальных процессов в системе.

Пример многопоточного приложения можно взять, например, у меня на сайте:
http://www.schevchenko.net.ru/SRC/SuperMarket_50.zip

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

Удачи!


 
evvcom ©   (2005-06-02 08:51) [65]


> Alexander Panov ©   (01.06.05 17:45) [55]
> evvcom ©   (01.06.05 16:22) [53]
> А это еще зачем? Сама критическая секция уже и есть нужный
> "флажок".
>
> Нет, н флажок.
> Критическую секцию равноправно могут использовать как пишущий,
> так и читающий потоки.
...
> Для таких флагов подходят как семафоры, так и мьютексы.

Зачем на крит.секции еще и семафоры с мьютексами городить? Чем они реально помогут? Будут те же WaitForSingleObject как в читающем, так и пишущем потоках. Результат тот же, те же остановки и переключения на другой поток, только будет использовано 2 объекта синхронизации. Зачем все это? Для данной задачи вполне хватит одной крит. секции.


> Но R после установки флажка "Читаю" должен также дождаться,
> пока текущий W закончит записывать данные, если таковой
> существует, т.е. для этого необходим тоже некий флаг.

Во-во. И еще десяток флагов надо добавить.

2 leonidus: не вижу смысла даже менять приоритеты потоков. Парсинг будет выполнятся в любом случае горазде быстрее, чем подкачка данных из интернета. Поэтому в пишущем потоке я бы даже synchronize не стал вызывать, а использовал бы PostMessage. Сообщил основному потоку о завершении закачки, выбрал из очереди очередной линк и опять на закачку, если линки кончились, то suspend. Скорее всего тебе одного доп.потока хватит для парсинга результатов от всех потоков закачки, т.к. количество скачанного все равно будет лимитироваться шириной твоего интернет-канала. Не надо усложнять там, где этого не требуется.


 
Alexander Panov ©   (2005-06-02 09:37) [66]

evvcom ©   (02.06.05 8:51) [65]

Ты разве еще не понял, что читающий поток может вообще не получить управления?


 
Alexander Panov ©   (2005-06-02 09:39) [67]

evvcom ©   (02.06.05 8:51) [65]
Зачем на крит.секции еще и семафоры с мьютексами городить


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

evvcom ©   (02.06.05 8:51) [65]
Во-во. И еще десяток флагов надо добавить.


Вообще-то это делается при необходимости. А ты можешь добавлять хоть 2 десятка.

evvcom ©   (02.06.05 8:51) [65]
Результат тот же, те же остановки и переключения на другой поток, только будет использовано 2 объекта синхронизации. Зачем все это? Для данной задачи вполне хватит одной крит. секции.


Не вводи в заблуждение автора вопроса.


 
Digitman ©   (2005-06-02 09:45) [68]


> Alexander Panov ©   (01.06.05 23:10) [63]
> На память не помню, имеет ли основной поток равные приоритеты
> с дополнительными


имеет.

основной создается системой с THREAD_PRIORITY_NORMAL по дифолту

дополнительные, если не приложить свои шаловливые ручки к потрохам CreateThread, создаются с тем же приоритетом по дифолту

The thread is created with a thread priority of THREAD_PRIORITY_NORMAL (цитата из справки к CreateThread)


 
evvcom ©   (2005-06-02 10:05) [69]


> Ты разве еще не понял, что читающий поток может вообще не
> получить управления?

Пальцем можно ткнуть, что помешает?


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

Но я нигде не встречал и того, что семафоры и мьютексы помогают "выбрать нужный поток для приоритетного выполнения". Для этого меняют приоритет самого потока. Можно и здесь пальцем ткнуть, если считаешь, что я не прав. Обсудим.


 
Alexander Panov ©   (2005-06-02 10:57) [70]

evvcom ©   (02.06.05 10:05) [69]
Пальцем можно ткнуть, что помешает?


Достаточно простого примера.

У тебя сотня потоков. Из них один читающий, 99 пишущих.
Предположим, для записи каждому пишущему потоку нужно 1 сек.
Все потоки запросили критическую секцию.
В какой момент читающий поток сможет получить эту критическую секцию?
Ответ такой - это неизвестно. он может начать выполняться сразу, а может и через минуту.

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

Но я нигде не встречал и того, что семафоры и мьютексы помогают "выбрать нужный поток для приоритетного выполнения". Для этого меняют приоритет самого потока. Можно и здесь пальцем ткнуть, если считаешь, что я не прав. Обсудим.


А никто и не говорил, что объекты синхронизации изменяют приоритеты. Они лишь помогают выстроить логику программы так, как нужно тебе.
Установка приоритета гарантирует тебе то, что нужный поток будет получать больше квантов времени для выполнения.
Причем повышенный приоритет потока может тебе "помочь" наоборот.
В данном конкретном случае без использования объектов синхронизации поток с повышенным приоритетом будет использовать максимально возможное количество времени процессора, что приведет к "голоданию" остальных потоков.


 
evvcom ©   (2005-06-02 11:30) [71]


> > Ты разве еще не понял, что читающий поток может вообще не
> > получить управления?


> evvcom ©   (02.06.05 10:05) [69]
> Пальцем можно ткнуть, что помешает?
>
> Достаточно простого примера.

C примером согласен. Но если

> программа просто будет терминирована недождавшимся ответа
> пользователем

то это уже форс-мажор, и этим я бы не стал все же объяснять выражение "вообще не получить управления". А чтобы не нервировать пользователя, я уже писал, что крит.секциями стоит защищать быстро выполняемые куски кода, чтобы не тормозить работу остальных потоков, использующих эти же данные. В примере автора запись происходит всего в 2 строчках кода, их мы и защищаем. Аналогично с чтением.


> поток с повышенным приоритетом будет использовать максимально
> возможное количество времени процессора, что приведет к
> "голоданию" остальных потоков

Так ведь есть вариант с понижением приоритета. О нем, кажется, Игорь упомянул. И все же я не увидел объяснения целесообразности употребления доп. флажков (семафора, мьютекса) в данном примере.


 
Alexander Panov ©   (2005-06-02 11:52) [72]

evvcom ©   (02.06.05 11:30) [71]

Так ведь есть вариант с понижением приоритета. О нем, кажется, Игорь упомянул. И все же я не увидел объяснения целесообразности употребления доп. флажков (семафора, мьютекса) в данном примере.


А я не увидел предложенного решения от тебя(методологии, алгоритм). Или я что-то упустил?
Применение объектов синхронизации(мьютексов, событий, семафоров) в классе данных задач - с арбитражем конкурирующих за ресурс потоков(несколько писателей, один читатель, или подобных) - стандартный прием.

А проблему с захватом процессорного времени одним потокм ты уже решил?


 
evvcom ©   (2005-06-02 12:19) [73]


> А я не увидел предложенного решения от тебя(методологии,
> алгоритм). Или я что-то упустил?

Предложения не сформулированы в одном моем посте, это надо перечитывать все обсуждение. А алгоритм подправленный всеми автор выложил в итоге сам (тоже в нескольких постах).

> А проблему с захватом процессорного времени одним потокм

Здесь нет этой проблемы, Имхо. И здесь потоки держат критическую секцию не по 1 секунде. Даже у сотни потоков на разовую запись уйдет меньше секунды.

По-моему, мы углубились в такие дебри и пытаемся раздуть из мухи слона там, где проблема выеденного яйца не стоит. Все имхо.


 
Alexander Panov ©   (2005-06-02 12:26) [74]

evvcom ©   (02.06.05 12:19) [73]
По-моему, мы углубились в такие дебри и пытаемся раздуть из мухи слона там, где проблема выеденного яйца не стоит. Все имхо.


Это не дебри.
Это элементарная тщательность при проектировании многопоточного приложения.

То, что запись происходит бысто - это не аргумент.

evvcom ©   (02.06.05 12:19) [73]
Здесь нет этой проблемы, Имхо. И здесь потоки держат критическую секцию не по 1 секунде. Даже у сотни потоков на разовую запись уйдет меньше секунды.


А читающий поток?


 
Alexander Panov ©   (2005-06-02 12:29) [75]

>evvcom ©

При проектировании многопоточного потока не бывает мелочей, на которые не стоит обращать внимание.

Любая мелочь может сыграть роковую роль для приложения.
-------------
PS.
А потом говорим, что приложение падает непонятно почему.


 
evvcom ©   (2005-06-02 12:50) [76]


> А читающий поток?

А что читающий поток? Ни один из потоков не захватывает время настолько, чтобы заметно мешать другим потокам. И после того как пишущий делает свое дело, он извещает об этом читателя. Это гарантирует, что читающий обработает все данные.


> Любая мелочь может сыграть роковую роль для приложения.

С этим полностью согласен.


 
Digitman ©   (2005-06-02 12:55) [77]


> Alexander Panov


> evvcom


не кажется ли вам. уважаемые судари, что сабж перешел в статус "мерянье концами" ?)


 
Alexander Panov ©   (2005-06-02 13:19) [78]

evvcom ©   (02.06.05 12:50) [76]
И после того как пишущий делает свое дело, он извещает об этом читателя.


Как извещает? И что делает в это время читающий поток?


 
evvcom ©   (2005-06-02 13:54) [79]


> Как извещает?

мне нравится PostMessage, хотя по ситуации

> И что делает в это время читающий поток?

Во время оповещения читающий поток ждет, когда ему ОС выделит квант времени. :)

> не кажется ли вам.

Мне это уже давно кажется. Надо завязывать.


 
Erik1 ©   (2005-06-02 15:17) [80]

Всегда тут не по теме треплются.
Автору
Советую обратить внимание на группу функций WaitFor(MultiObject). Для опредерения необходимости запуска потоков. Допустим такую ситуацию, пишуший поток вошол в критическаю секцию и завис на обашении к сети. Вся наша система встала, даже трудно понять, что произошло. Для облегчения взвимодействия потоков применяются события и функции ожидающие этих событий. Причем событий может быть несколько, например сигнал аваийного звершения задачи.

P.S
Свой код могу выложить или выслать по почте.


 
Alexander Panov ©   (2005-06-02 15:41) [81]

evvcom ©   (02.06.05 13:54) [79]
...Во время оповещения читающий поток ждет, когда ему ОС выделит квант времени. :)

...И после того как пишущий делает свое дело, он извещает об этом читателя.

...мне нравится PostMessage, хотя по ситуации


Еще раз.

1. Если ожидающий поток ждет кванта времени, это означает, что он не назодится в сотоянии ожидания одного из объектов ядра?

2. Нахрена извещать поток, если он всего лишь "ожидает кванта времени"?

3. С какого перепугу поток вдруг начнет обрабатывать PostMessage?
---------------------

Может хватит чушь нести?


 
Alexander Panov ©   (2005-06-02 15:55) [82]

Прошу прощения за резкое высказывание.
Предлагаю перенести дискуссию в потрепаловку.


 
evvcom ©   (2005-06-02 17:20) [83]

Перенес http://delphimaster.net/view/15-1117717481/


 
GrayFace ©   (2005-06-02 21:28) [84]

leonidus
См. книгу Рихтера "Windows для профессионалов". По слухам, есть на http://www.podgoretsky.com


 
GrayFace ©   (2005-06-02 21:40) [85]

evvcom ©   (02.06.05 8:51) [65]
Поэтому в пишущем потоке я бы даже synchronize не стал вызывать, а использовал бы PostMessage.

Поддерживаю.


 
Alexander Panov ©   (2005-06-02 23:17) [86]

GrayFace ©   (02.06.05 21:40) [85]
Поддерживаю.

Аналогично, как и перед этим. В топике в потрепаловке есть где разгуляться.


 
leonidus ©   (2005-06-03 08:27) [87]

Блин вот это тред мы тут развели:) Может действительно усложнили задачу... в начале все так элегантно получалось. Я одного не пойму, если критическая секция априори допускает в себя только один поток, то значит мы этим механизмом однозначно не допустим параллельной записи и одновременного чтения из массива, что нам собственно и нужно. Но тут накладывается проблема чтения массива в произвольный момент времени. Ну так она должна решаться просто повышением приоритета читающего потока над записывающим. Т.е. если записывающий (W) уже в секции, читающий(R) все равно бессилен, но зато когда W закончит свою работу управление передасться R а не следующему W, а считав нужные данные W передаст эстафету следующему R что и требовалось доказать. Я правильно мыслю?



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

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

Наверх




Память: 0.9 MB
Время: 0.042 c
6-1112087491
Sapsi
2005-03-29 13:11
2005.06.29
найти все компьютеры в сети


1-1118035496
СержК
2005-06-06 09:24
2005.06.29
Как запустить процедуру DBGrid4KeyPress(...


1-1117610623
pudel
2005-06-01 11:23
2005.06.29
Вывод текста в заданном прямоугольнике


4-1115100803
nomshar
2005-05-03 10:13
2005.06.29
Copy/paste


1-1118169137
Ralf
2005-06-07 22:32
2005.06.29
Form





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
Английский Французский Немецкий Итальянский Португальский Русский Испанский