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

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

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

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

Сообщение vladgul » 29.01.2023 15:48:06

Нашел некоторую альтернативу 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);

Кто нибудь сталкивался с подобными проблемами?
vladgul
незнакомец
 
Сообщения: 5
Зарегистрирован: 27.12.2022 18:53:24

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

Сообщение MiniQ » 30.01.2023 13:09:19

Насколько я понимаю, работа с визуальными компонентами LCL должны производиться из основного потока, не думаю, что вызов Memo1.Lines.Add потокобезопасен
MiniQ
новенький
 
Сообщения: 81
Зарегистрирован: 28.01.2013 16:31:55

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

Сообщение Alex2013 » 31.01.2023 10:12:25

Варианты решения проблем потоков чрез использование Sleep крайне не надежны .
Есть два варианта или краткое возвращение в основной поток помощью метода Synchronize (что тормозит поток ) или проверка флага по таймеру ( таймер это не поток и доступ из его обработчика есть к чему угодно и единственная забота, в том, что нужно продумать защиту от повторного вхождения ( если процедура обработки события таймера не завершилась до того как начался ее следующий вызов это что называется "чревато боком" ) ) + Естественно нужно завершить передачу данных в буферную переменную и только потом поднималась флаг .
Alex2013
долгожитель
 
Сообщения: 2922
Зарегистрирован: 03.04.2013 11:59:44

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

Сообщение vladgul » 01.02.2023 14:56:28

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
vladgul
незнакомец
 
Сообщения: 5
Зарегистрирован: 27.12.2022 18:53:24

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

Сообщение Mikhail » 01.02.2023 15:19:42

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

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


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

Точно не помню (нужно смотреть исходники), но, вроде бы при разрушении потока все методы поставленные в очередь этим потоком удаляются.
Mikhail
энтузиаст
 
Сообщения: 562
Зарегистрирован: 24.10.2013 16:06:47

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

Сообщение zoltanleo » 02.02.2023 00:46:41

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

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

Зы. Пугают меня всякие компоненты-посредники. Имхо, пишутся для лентяев, которым день разбираться в коде ;)
Аватара пользователя
zoltanleo
постоялец
 
Сообщения: 457
Зарегистрирован: 17.10.2013 10:55:01

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

Сообщение Mikhail » 02.02.2023 10:00:10

Вот деструктор
Код: Выделить всё
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; 
Mikhail
энтузиаст
 
Сообщения: 562
Зарегистрирован: 24.10.2013 16:06:47

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

Сообщение Alex2013 » 02.02.2023 12:25:16

vladgul писал(а):Либо через вызов TThread.Queue(nil,Proc1); поставив таким образом в очередь исполнения в основном потоке без притормаживания текущего (параллельного).

Сталкивался с "загадочной" путаницей в порядке очередности исполнения очереди запросов. ( на самом деле, разумеется, ничего загадочного в этом нет, но результаты иногда достаточно странные и нужно заранее предусматривать такую возможность, даже если в программе всего один дополнительный поток ) С таймером "стабилизированный код" получается как-то попроще. :idea:
Alex2013
долгожитель
 
Сообщения: 2922
Зарегистрирован: 03.04.2013 11:59:44

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

Сообщение vladgul » 04.02.2023 17:28:03

zoltanleo писал(а):Если в основном потоке дошла очередь до обработки сообщения, а доп.поток, который это сообщение, уже умер, то в лучшем случае сообщение будет пустое, в худшем - словишь AV.
Вот это внесло ясность. Спасибо.

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


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 33

Рейтинг@Mail.ru