![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
TThread + Synchronize = bug [D7, WinXP]
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.