Потоки. Синхронизация с основным потоком.

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Ответить
vladgul
незнакомец
Сообщения: 5
Зарегистрирован: 27.12.2022 17:53:24

Потоки. Синхронизация с основным потоком.

Сообщение vladgul »

Нашел некоторую альтернативу TTask из Delphi для организации пула потоков.
Называется EZThreads (https://github.com/mr-highball/ezthreads)
Предварительно потестировал, в целом понравилось.
Но столкнулся с проблемой потери вызовов из очереди основного потока

В качестве тестового примера
В теле процедур (всего 15шт) исполняемых в потоках
Делаю

Код: Выделить всё

 begin
   Sleep(1000) ... Sleep(15000) в каждом потоке своя задержка.
    TThread.Queue(nil, Proc1); .. TThread.Queue(nil, Proc15);  - соответственно для каждого потока своя  процедура
 end;
Код процедур Proc1 ... Proc15

Код: Выделить всё

begin
  Memo1.Lines.Add('1'); ... Memo1.Lines.Add('15');  
end;
Насколько я понимаю после выполнения всех потоков в Memo1 должны быть выведены все уведомления от всех потоков. Но могут быть в разном порядке в частных случаях.

Проблема в том, что в Memo1 выводятся лишь некоторые (всего 2-3 штуки сообщения, бывают разные, иногда вообще ничего).
Такое ощущение , что они как-то теряются.
Если заменить

Код: Выделить всё

TThread.Queue(nil, Proc1);
на
TThread.Synchronize(nil, Proc1);
то все работает как ожидается.

Не думаю, что это связано именно с этой библиотекой.
Вопрос к тем, кто активно использует потоки и разную синхронизацию из них c основным потоком.
Встречались ли проблемы с несрабатыванием методов поставленных в очередь через TThread.Queue ?
Причем, если добавить
после

Код: Выделить всё

TThread.Queue(nil, ProcN); 
и поставить задержку
Sleep(15) - то начинает выводиться
А Sleep(5) не хватает.
Стремный какой-то вариант, может на других компах не хватит и Sleep(1000);

Кто нибудь сталкивался с подобными проблемами?
MiniQ
новенький
Сообщения: 81
Зарегистрирован: 28.01.2013 16:31:55

Сообщение MiniQ »

Насколько я понимаю, работа с визуальными компонентами LCL должны производиться из основного потока, не думаю, что вызов Memo1.Lines.Add потокобезопасен
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Варианты решения проблем потоков чрез использование Sleep крайне не надежны .
Есть два варианта или краткое возвращение в основной поток помощью метода Synchronize (что тормозит поток ) или проверка флага по таймеру ( таймер это не поток и доступ из его обработчика есть к чему угодно и единственная забота, в том, что нужно продумать защиту от повторного вхождения ( если процедура обработки события таймера не завершилась до того как начался ее следующий вызов это что называется "чревато боком" ) ) + Естественно нужно завершить передачу данных в буферную переменную и только потом поднималась флаг .
vladgul
незнакомец
Сообщения: 5
Зарегистрирован: 27.12.2022 17:53:24

Сообщение vladgul »

MiniQ писал(а):Насколько я понимаю, работа с визуальными компонентами LCL должны производиться из основного потока, не думаю, что вызов Memo1.Lines.Add потокобезопасен
С этим как раз все в порядке
код

Код: Выделить всё

Procedure Proc1;
begin
  Memo1.Lines.Add('1'); ... Memo1.Lines.Add('15'); 
end;
Работает в контексте основного потока через TThread.Queue(nil, Proc1);
Или через Synchronize
Alex2013 писал(а):Варианты решения проблем потоков чрез использование Sleep крайне не надежны .
С этим полностью согласен.

Суть примера была не в том, что там разместить в коде, а возможный глюк, когда через Synchronize все работает как ожидается, но при этом тормозит на время выполнения метода параллельный процесс и работает в контексте основного потока, пока не отработает до конца. Конечно нужно стараться такие методы сделать как можно короче.
Либо через вызов TThread.Queue(nil,Proc1); поставив таким образом в очередь исполнения в основном потоке без притормаживания текущего (параллельного).
Здесь столкнулся с тем, что поставленные в очередь таким образом процедуры не получают управления. Вот это и странно и хотелось бы с этим разобраться.
Предполагаю, что сделав некоторую задержку на выходе из тела процесса, после вызова TThread.Queue() поток успевает поставить сообщение в очередь основного процесса и что-то еще сделать "за кулисами" для запуска "Proc1".
А без задержки контекст потока прерывается и метод не стартует, теряется вызов.
Вот расписал это и думаю, что тут уже может быть дело в самой библиотеке EZThreads
Mikhail
энтузиаст
Сообщения: 565
Зарегистрирован: 24.10.2013 16:06:47

Сообщение Mikhail »

vladgul писал(а):Встречались ли проблемы с несрабатыванием методов поставленных в очередь через TThread.Queue ?
Причем, если добавить
после

Код: Выделить всё
TThread.Queue(nil, ProcN);


и поставить задержку
Sleep(15) - то начинает выводиться
А Sleep(5) не хватает.
Стремный какой-то вариант, может на других компах не хватит и Sleep(1000);
Точно не помню (нужно смотреть исходники), но, вроде бы при разрушении потока все методы поставленные в очередь этим потоком удаляются.
Аватара пользователя
zoltanleo
постоялец
Сообщения: 459
Зарегистрирован: 17.10.2013 10:55:01

Сообщение zoltanleo »

Queue - аналог PostMessage
Synchronize - аналог SendMessage

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

Зы. Пугают меня всякие компоненты-посредники. Имхо, пишутся для лентяев, которым день разбираться в коде ;)
Mikhail
энтузиаст
Сообщения: 565
Зарегистрирован: 24.10.2013 16:06:47

Сообщение Mikhail »

Вот деструктор

Код: Выделить всё

destructor TThread.Destroy;
begin
  if not FExternalThread then begin
    SysDestroy;
    if FHandle <> TThreadID(0) then
      CloseThread(FHandle);
  end;

  RemoveQueuedEvents(Self);

  DoneSynchronizeEvent;
  { set CurrentThreadVar to Nil? }
  inherited Destroy;
end;  
Alex2013
долгожитель
Сообщения: 3211
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

vladgul писал(а):Либо через вызов TThread.Queue(nil,Proc1); поставив таким образом в очередь исполнения в основном потоке без притормаживания текущего (параллельного).
Сталкивался с "загадочной" путаницей в порядке очередности исполнения очереди запросов. ( на самом деле, разумеется, ничего загадочного в этом нет, но результаты иногда достаточно странные и нужно заранее предусматривать такую возможность, даже если в программе всего один дополнительный поток ) С таймером "стабилизированный код" получается как-то попроще. :idea:
vladgul
незнакомец
Сообщения: 5
Зарегистрирован: 27.12.2022 17:53:24

Сообщение vladgul »

zoltanleo писал(а):Если в основном потоке дошла очередь до обработки сообщения, а доп.поток, который это сообщение, уже умер, то в лучшем случае сообщение будет пустое, в худшем - словишь AV.
Вот это внесло ясность. Спасибо.
zoltanleo писал(а): Зы. Пугают меня всякие компоненты-посредники. Имхо, пишутся для лентяев, которым день разбираться в коде ;)
Не совсем понятно, какой код имеется ввиду.
Классический потомок от TThread или TThread.CreateAnonymousThread. Или имеется ввиду что-то другое?
В целом частично согласен. Но изобретать велосипеды, когда уже полно готовых не всегда оправдано, но зато как увлекательно, когда есть время :-).
Ответить