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

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

TThread + Synchronize = bug [D7, WinXP]


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

abb777 ©   (2010-12-19 21:25:00) 

Hi, All,

Не могу понять, в чем дело. Вроде все, как надо, сделал.
Есть несколько одинаковых нитей, взаимодействующих через события (CreateEvent() / WaitForSingleObject() ) друг с другом. В методе Execute вызывается Synchronize() для отображения хода выполнения каждой нити.

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

Проблема в принудительном останове. Все виснет, если в основной нити VCL просто вызвать нечто типа

 for i := 0 to FLayers.Count - 1 do
 begin
   FLayers.Layer[i].FThread.Terminate;
 end;


Данный код просто завешивает все нити. Они висят, как видно в отладочном окне "Threads" на некоем вызове WaitForSingleObject(), но, насколько я понял, это не "мой" WaitForSingleObject() из метода Execute(), а некий другой. Внутри Execute() все с виду ОК - Terminated там анализируется. Да и дело, похоже не в этом. Если я закомментарю вызов Synchronize(), то все останавливается ОК, все нити завершаются, как только я выйду из кода, приведенного выше.

Исходя из этого факта я предположил, что зависание происходит в коде самого TThread, а именно здесь:

class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);
..............
       LeaveCriticalSection(ThreadLock);
       try
         WaitForSingleObject(SyncProc.Signal, INFINITE); <<<<<<<<<
       finally
         EnterCriticalSection(ThreadLock);
       end;
...................


Перепробовал кучу всяких изворотов, убрал Synchronize() из этих нитей, которых может быть и 100 (сейчас только 10), и создал спец. нить для отображения, а всю информацию для отображения "рабочие" нити валят в список, который разгребает эта "нить отображения". Тот же эффект. Все работает и завершается по окончанию метода Execute() нормально, но при принудительно/досрочном завершении все виснет. Причем виснут все нити, а не только та одна, где сейчас есть вызов Synchronize.

Все операции с общими объектами сделаны через критическую секцию, так что с этим все д.б. в порядке.

Кто-нибудь такое встречал?
Есть идеи?

Заранее спасибо.


han_malign   (2010-12-20 15:07:00) [41]


> Так вопрос как раз в том и есть, как это сделать правильно

- правильно - не тормозить расчеты ожиданием синхронизации с основным потоком...

В некоторых случаях лучше делать опрос состояния потоков из основного(по таймеру, или в Application.OnIdle, или в PostMessage нотификации).

Чтобы не тормозить потоки на получении состояния:
1. в критическую секцию надо оборачивать только изменение/получение описательной части состояния. Лучше сделать описательную часть состояния атомарной - выделить в структуру, обновляющуюся в узловых точках алгоритма.
2. "длинные счетчики `реального времени`" масштабировать на процентную шкалу, с InterlockedXxx синхронизацией только по определённому порогу(Memory Barrier - синхронизация каждой итерации может дать серьезное пенальти производительности).
3. Проверять необходимость "большой синхронизации" по короткому штампу состояния(например счетчик изменений + последнее считанное изменение, или тупой самосбрасываемый Changed-флаг; семафор - для мягкого реального времени с пропуском кадров визуализации - не очень подходит)
4. Дополнительно урезать период опроса проверкой какого либо счетчика реального времени(обычно достаточно GetTickCount). Даже в случае таймера(с пропуском лишних WM_TIMER), если время обработки состояния сравнимо с периодом таймера, возможно уплотнение шкалы опроса(следствие из теоремы Котельникова), не говоря об OnIdle и переполнении стека на не проверяемых PostMessage...
5. И никаких Repaint - фиксация состояния + Invalidate, если за то время пока дойдет до OnPaint, состояние еще несколько раз изменится - то это единственный правильный сценарий для системы мягкого реального времени...

З.Ы. Если у вас самоцель полчаса любоваться на каждыйкадр состояния, вместо того, чтобы завершить расчет за 15 секунд - все таки подумайте о логах...


Ega23   (2010-12-20 14:49:00) [42]


>  Это не будет threadsafe, насколько я понимаю.

Почему? Всё будет ровно так, как ты напишешь.

> Там нет Handle, а я не знаю, как ловить формой такие сообщения.

Ну у твоего объекта-то Handle есть? Есть. Вот ему и шли. Если нет - заведи.

>  Конкретный пример, вероятно, помог бы.

Ну я вечером из дома постараюсь пример привести, если силы будут.


abb777   (2010-12-20 14:00:00) [43]

> 1. В свой Execute вставляй частую проверку if Terminated then ...

Так я уже и так там навставлял кучу раз. Во всех циклах и после каждой затяжной процедуры. Куда уж чаще...

> 2. Откажись от Synchronize, используй SendMessage

Вот с этого момента поподробней. Как может помочь SendMessage? Это не будет threadsafe, насколько я понимаю. Я пробовал использовать PostThreadMessage() но ничего не получил. Там нет Handle, а я не знаю, как ловить формой такие сообщения. Конкретный пример, вероятно, помог бы.


abb777   (2010-12-20 13:53:00) [44]

>> if FEngine <> nil then
>>   FEngine.FDispThread := nil;
> Вот это вот не понял.

Это для того, чтобы внутри TREngine не вызывать FDispThread.Terminate, если она на данный момент уже завершилась. Чисто для страховки объект говорит своему родителю - "меня уже нет".


abb777   (2010-12-20 13:47:00) [45]

> я так подозреваю, что ошибка у вас в коде, выполняющемся после

А что конкретно не так? Я там пробовал столько всяких вариантов... Включал все в крит. секцию, делал без секции, пробовал завершать и дожидаться завершения каждой нити по порядку... Ничего не помогло. Единственное, что как-то сказалось на результате - это вставка CheckSynchronized() в самое начало метода CancelIteration(). Но тоже как-то все очень нестабильно.

Вопрос-то вроде бы просто формулируется - как мне убедиться из своего кода, что все Synchronize() уже проработали? И почему об этом требовании нигде не написано? Я, по крайней мере, нигде такого требования не нашел.


Ega23   (2010-12-20 13:40:00) [46]

if FEngine <> nil then
  FEngine.FDispThread := nil;

Вот это вот не понял.


Ega23   (2010-12-20 13:38:00) [47]


> Все работает и завершается по окончанию метода Execute()
> нормально, но при принудительно/досрочном завершении все
> виснет.


1. В свой Execute вставляй частую проверку if Terminated then...
2. Откажись от Synchronize, используй SendMessage


abb777   (2010-12-20 13:37:00) [48]

Specially for LVT--
Я понимаю, и уже давно, все, что Вы, Леонид, хотите сказать. Но для того, чтобы сделать компиляцию легким движением нужен специальный тестовый проект, поскольку в реальном используются другие модули, откуда берется куча всяких переменных. Я это сделаю, но только в том случае, если не услышу никаких советов от опытных людей, реально строивших многопоточные программы. И в любом случае разместить этот тест на форуме просто нереально. Но я дам урл, откуда его скачать.

To Дмитрий Белькевич:

>>FTerminating
>Зачем этот флаг? Terminated треда не достаточно?

Наследство от отладки. Я пытался сначала проставлением этого флага вывести каждый Execute() на самый конец, где уже нет никаких WaitForSingleObject(), а есть только цикл типа

while not Terminated do
begin
 Sleep(0);
end;


а уже затем последовательно вызвать Terminate для каждой нити и дождаться в цикле пока все нити закроются. Я не мог понять, на каком-таком WaitForSingleObject() зависают все нити. Я сначала думал, что это в моем алгоритме что-то глючит. Потом догадался, что это не совсем у меня.

>>Synchronize
>Не вижу в коде ни одного.

Пардон. Я выпустил это кусок из виду. Я же говорю, форум не дает запостить весь модуль целиком. Вот это место:

constructor TREDisplayThread.Create(AEngine : TREngine);
begin
 inherited Create(true);
 FEngine := AEngine;
 FreeOnTerminate := true;
 Priority := tpIdle;
 Resume;
end;

destructor TREDisplayThread.Destroy;
begin
 if FEngine <> nil then
   FEngine.FDispThread := nil;
 inherited Destroy;
end;

procedure TREDisplayThread.Execute;
begin
 while not Terminated do
 begin
   Synchronize(FEngine.DoDisplay);
   Sleep(0);
 end;
end;


Сам объект TREDisplayThread -- это поле TREngine.FDispThread. Создается в TREngine.NewIteration;, уничтожается в TREngine.CancelIteration. Раньше Synchronize() был в каждой "рабочей" нити TLayerThread. Затем я сделал для него отдельную нить. Не особо помогло :(

> Что если попробовать вместо Terminate связку Terminate + WaitFor?

А код конкретный нельзя ли привести?
И куда это вставить?


Cobalt   (2010-12-20 13:22:00) [49]

я так подозреваю, что ошибка у вас в коде, выполняющемся после
for i := 0 to FLayers.Count - 1 do
begin
  FLayers.Layer[i].FThread.Terminate;
end;


Leonid Troyanovsky   (2010-12-20 10:02:00) [50]


> abb777 ©   (19.12.10 23:19) [8]

> А я как раз об этом. Весь проект я приводить и не собирался.

Ну, а где ж достаточность? Под достаточностью подразумеваем:
если уж нашелся желающий воспроизвести пример, то он
не придется гадать, что такое TREngine, FLayer и куда засунуть
procedure CalculateState, а сможет легким движением руки
скопировать код и откомпилировать у себя.

--
Regards, LVT.


Дмитрий Белькевич   (2010-12-20 09:27:00) [51]

>Если я закомментарю вызов Synchronize(),

Что если попробовать вместо Terminate связку Terminate + WaitFor?


Дмитрий Белькевич   (2010-12-20 09:21:00) [52]

>FTerminating

Зачем этот флаг? Terminated треда не достаточно?

>Synchronize

Не вижу в коде ни одного.


abb777   (2010-12-19 23:19:00) [53]

>> Привести весь код не позволяет форум.

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

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

Попробовал использовать CheckSynchronize(0); в начале CancelIteration(). Если прервать процесс почти сразу после начала, то сбой 100%, а если подождать секунд 5-6, а затем прервать, то выходит корректно. Внутри Execute с виду никой специфики нет в зависимости от числа шагов. В качестве эмулятора расчетной процедуры используется ГСЧ типа


procedure CalculateState;
var
 x : Double;
begin
 while true do
 begin
   x := Random;
   if (x > 0.5) and (x < 0.501) then break;
 end;
end;


Leonid Troyanovsky   (2010-12-19 23:04:00) [54]


> abb777 ©   (19.12.10 22:34) [4]

> Привести весь код не позволяет форум.

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

--
Regards, LVT.


abb777   (2010-12-19 22:41:00) [55]


> Здесь дед лок.
> Крит секции не нужны, уберите

Откуда? Это кусок из Classes.pas.

У меня крит. секции используются только там, где надо. Я так думаю.

А насчет

После остановки потока во время работы метода synchronize в деструкторе потока происходит завешивание основного потока при выполнении WaitForSingleObject, который ждёт заершения уже завершённого потока.

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


_Юрий   (2010-12-19 22:40:00) [56]

да, это я мощно задвинул
бываит :-)


abb777   (2010-12-19 22:34:00) [57]

Мало кода? ОК. Держите. Только будет ли кто-то в этом разбираться?... Терзают смутные сомнения... Я надеялся, что кто-нить встречал такой СИМПТОМ.

Проблема возникает при вызове  TREngine.CancelIteration.
Такие строчки, как   if FRCS.DebugInfo = nil then Exit; или if Self = nil then Exit; вставлены чисто для отладки, так что за них пинать не надо, я надеюсь, что суть не в них.

Привести весь код не позволяет форум.


procedure TLayerThread.Execute;
begin
 EnterCriticalSection(FRCS);
 FLayer.FCurStep := 0;
 FLayer.FIsActive := true;
 FLayer.FIsReady := false;
 FLayer.FIsWaiting := false;
 LeaveCriticalSection(FRCS);
 while true do
 begin
   // принудительный выход
   if Terminated or
      FLayer.FTerminating or
      (FLayer.FEvent = INVALID_HANDLE_VALUE) then
   begin
     FLayer.FTerminating := true;
     break;
   end;
   // флаг реверсивности
   Ffr := FLayer.FCurZone.FIsReversed;
   // флаг ожидания - истина для всех внутренних слоев или
   // для нижнего слоя при реверсивной подаче
   // для верхнего слоя при нормальной подаче
   Fwu := FLayer.FUpperLayer <> nil;
   Fwl := FLayer.FLowerLayer <> nil;
   Ffw := (Fwu or not Ffr) and (Fwl or Ffr);
   if Ffw then
   begin
     Fres := WAIT_TIMEOUT;
     repeat
       //
       if Terminated or
          FLayer.FTerminating or
          (FLayer.FEvent = INVALID_HANDLE_VALUE) then
       begin
         FLayer.FTerminating := true;
         break;
       end;
       //
       if Fwu and Ffr then
       begin
         EnterCriticalSection(FRCS);
         if (FLayer.FUpperLayer.FCurStep > FLayer.FCurStep) or
            (FLayer.FUpperLayer.FCurStep = FLayer.FCurStep) and
            (FLayer.FUpperLayer.FIsReady) then
         begin
           Ffw := false;
           ResetEvent(FLayer.FEvent);
         end;
         LeaveCriticalSection(FRCS);
       end           else
       if Fwl and not Ffr then
       begin
         EnterCriticalSection(FRCS);
         if (FLayer.FLowerLayer.FCurStep > FLayer.FCurStep) or
            (FLayer.FLowerLayer.FCurStep = FLayer.FCurStep) and
            (FLayer.FLowerLayer.FIsReady) then
         begin
           Ffw := false;
           ResetEvent(FLayer.FEvent);
         end;
         LeaveCriticalSection(FRCS);
       end;
       if Ffw then
       begin
         FLayer.FIsWaiting := true;
         Fres := WaitForSingleObject(FLayer.FEvent, 100);
         FLayer.FIsWaiting := false;
       end    else
       begin
         break;
       end;
     until Fres <> WAIT_TIMEOUT;
   end;
   if Terminated or
      FLayer.FTerminating or
      (FLayer.FEvent = INVALID_HANDLE_VALUE) then
   begin
    FLayer.FTerminating := true;
    break;
   end;
   //
   FLayer.CalculateState1;
   //
   if Terminated or
      FLayer.FTerminating or
      (FLayer.FEvent = INVALID_HANDLE_VALUE) then
   begin
     FLayer.FTerminating := true;
     break;
   end;
   //
   EnterCriticalSection(FRCS);
   FLayer.FIsReady := true;
   if Ffr then
     if Fwl and
        (FLayer.FLowerLayer.FCurStep = FLayer.FCurStep) and
        (FLayer.FLowerLayer.FEvent <> INVALID_HANDLE_VALUE) then
       SetEvent(FLayer.FLowerLayer.FEvent)
     else
   else
     if Fwu and
        (FLayer.FUpperLayer.FCurStep = FLayer.FCurStep) and
        (FLayer.FUpperLayer.FEvent <> INVALID_HANDLE_VALUE) then
       SetEvent(FLayer.FUpperLayer.FEvent);
   LeaveCriticalSection(FRCS);
   //
   FLayer.CalculateState2;
   if Terminated or
      FLayer.FTerminating or
      (FLayer.FEvent = INVALID_HANDLE_VALUE) then
   begin
      FLayer.FTerminating := true;
     break;
   end;
   //
   EnterCriticalSection(FRCS);
   if not FLayer.FTerminating then
     FLayer.FEngine.FDispList.Add(TREDisplayRec.Create(FLayer, FLayer.FCurStep));
   LeaveCriticalSection(FRCS);
   //
   if FLayer.FCurStep < FLayer.FList.Count - 1 then
   begin
     EnterCriticalSection(FRCS);
     Inc(FLayer.FCurStep);
     FLayer.FIsReady := false;
     LeaveCriticalSection(FRCS);
   end                                         else
   begin
     break;
   end;
 end;
 FLayer.FIsActive := false;
end;

procedure TREngine.CancelIteration;
var
 i : integer;
 f : boolean;
begin
 for i := 0 to FLayers.Count - 1 do
 begin
   FLayers.Layer[i].FTerminating := true;
   FLayers.Layer[i].FThread.Terminate;
 end;
 EnterCriticalSection(FRCS);
 FDispList.Clear;
 if FDispThread <> nil then
   FDispThread.Terminate;
 LeaveCriticalSection(FRCS);
 Sleep(0);
 repeat
   f := true;
   for i := 0 to FLayers.Count - 1 do
    if FLayers.Layer[i].FIsActive then
    begin
      f := false;
      break;
    end;
   Sleep(0);
 until f;
end;

procedure TREngine.DoDisplay;
var
 i : integer;
begin
 if FRCS.DebugInfo = nil then Exit;
 if Assigned(FOnStep) then
 begin
   begin
     EnterCriticalSection(FRCS);
     for i := 0 to FDispList.Count - 1 do
     begin
       FOnStep(Self,
               FDispList.DisplayRec[i].FLayer,
               FDispList.DisplayRec[i].FCurStep);
     end;
     FDispList.Clear;
     LeaveCriticalSection(FRCS);
   end;
 end;
end;


_Юрий   (2010-12-19 22:34:00) [58]


>   LeaveCriticalSection(ThreadLock);
>        try
>          WaitForSingleObject(SyncProc.Signal, INFINITE);
>  <<<<<<<<<
>        finally
>          EnterCriticalSection(ThreadLock);
>        end;


Здесь дед лок.
Крит секции не нужны, уберите


Dimka Maslov   (2010-12-19 22:00:00) [59]

После остановки потока во время работы метода synchronize в деструкторе потока происходит завешивание основного потока при выполнении WaitForSingleObject, который ждёт заершения уже завершённого потока.


Leonid Troyanovsky   (2010-12-19 21:38:00) [60]


> abb777 ©   (19.12.10 21:25)  

> Есть идеи?

Ошибка в 17 строке.

--
Regards, LVT.


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

Наверх

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