Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: Igors от Июнь 23, 2021, 09:27



Название: waitForDone
Отправлено: Igors от Июнь 23, 2021, 09:27
Добрый день

Создал QThread (рабочую) и подбрасываю ей работу сигналами из главной нитки, все устраивает. Но в какой-то момент главная должна дождаться что у рабочей нет больше задач. Др словами нужен ф-ционал QThreadPool::waitForDone но для конкретной рабочей нитки. Как это сделать технично, не влезая в очереди, примитивы синхронизации и.т.п. ?

Спасибо


Название: Re: waitForDone
Отправлено: Racheengel от Июнь 29, 2021, 15:56
То есть надо дождаться завершения QThread ? Для этого есть сигнал QThread::finished()


Название: Re: waitForDone
Отправлено: Igors от Июнь 29, 2021, 16:28
То есть надо дождаться завершения QThread ? Для этого есть сигнал QThread::finished()
То есть нет, завершать рабочую никто не хотел (QThreadPool мы же не завершаем). WaitForDone должна вернуть упр-е когда рабочая не имеет больше задач и ожидает их (в своем exec)


Название: Re: waitForDone
Отправлено: Racheengel от Июнь 30, 2021, 15:44
Ну, тогда свой сигнал сделать, и бросать его, если задач нет.
Какая-то призрачная проблема, если честно.


Название: Re: waitForDone
Отправлено: Igors от Июнь 30, 2021, 17:13
Ну, тогда свой сигнал сделать, и бросать его, если задач нет.
Какая-то призрачная проблема, если честно.
Какой сигнал и как (или куда) его бросать? Кароч, "а можна пример"?  :)


Название: Re: waitForDone
Отправлено: RedDog от Июль 01, 2021, 14:36
Код:
void ThreadWrapper::threadFunc()
{
    while (!done)
        doWork();
   emit workFinished();
}


Название: Re: waitForDone
Отправлено: Igors от Июль 02, 2021, 06:06
Код:
void ThreadWrapper::threadFunc()
{
    while (!done)
        doWork();
   emit workFinished();
}
Здесь нет ожидания задачи, т.е. этот код годится для параллельного выполнения только одной, потом надо нитку перезапускать. Гораздо удобнее то что предлагает Qt по умоочанию: создать QThread и сделать start. Нитка войдет  в свой событийный цикл (exec). Теперь можно подкармливать ее задачами просто посылая ей сигналы (QueuedConnection). Как не раз жевалось в прошлом, лучше/грамотнее пулять сигналами не самой нитке, а "воркеру".

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


Название: Re: waitForDone
Отправлено: Racheengel от Июль 02, 2021, 16:09
Код:

void WorkerThread::run()
{
    while (!isInterruptionRequested())
   {
      while (hasSomethingToDo())
          doWork();

      Q_EMIT notingToDo();

      while (!hasSomethingToDo() && !isInterruptionRequested())
          sleep(1);      
    }
}


Внешний цикл крутится, пока кто-то не запросит interruption.
Пока есть чего делать, делаем doWork().
Как только работа закончилась, "пуляем" nothingToDo() и зависаем в "пустышке", пока снова работы не подвалит.
Чтоб ресурсы не жрались, делаем sleep/msleep (это важно, а то потог будет ждат около 10% проца впустую).


Название: Re: waitForDone
Отправлено: Igors от Июль 03, 2021, 08:50
Как это сделать технично, не влезая в очереди, примитивы синхронизации и.т.п. ?
А предлагая hasSomethingToDo мы признаем что очередь имеем/храним, а это не так уж мало еще кода. Вообще зачем использовать свой run вместо exec по дефаулт?  В чем "выйгрышь"? Испускание notingToDo некорректно, в этот (злополучный) момент главная как раз посылает новую работу, просто рабочая ее еще не увидела. Корявый (m)sleeo тоже "не украшает".

И как из главной дождаться момента "нет работы"? Требуется синхронка, а не отлов сигнала (к тому же неверного).


Название: Re: waitForDone
Отправлено: RedDog от Июль 03, 2021, 19:06
Код:
void ThreadWrapper::threadFunc()
{
    while (!done)
        doWork();
   emit workFinished();
}
Здесь нет ожидания задачи, т.е. этот код годится для параллельного выполнения только одной, потом надо нитку перезапускать. Гораздо удобнее то что предлагает Qt по умоочанию: создать QThread и сделать start. Нитка войдет  в свой событийный цикл (exec). Теперь можно подкармливать ее задачами просто посылая ей сигналы (QueuedConnection). Как не раз жевалось в прошлом, лучше/грамотнее пулять сигналами не самой нитке, а "воркеру".

Вот. Теперь (стартовый пост) надо дождаться  когда рабочая нитка сделает все свои текущие дела - не путать с завершением.
С чего вдруг перезапускать?
ThreadWrapper::moveToThread в конструкторе и будет постоянно крутиться в отдельном своем потоке.
А ThreadWrapper::threadFunc() это либо слот, либо через invokeMethod его дернуть в своем потоке.


Название: Re: waitForDone
Отправлено: Igors от Июль 04, 2021, 11:34
С чего вдруг перезапускать?
ThreadWrapper::moveToThread в конструкторе и будет постоянно крутиться в отдельном своем потоке. А ThreadWrapper::threadFunc() это либо слот, либо через invokeMethod его дернуть в своем потоке.
Опять-таки, требуется (синхронное) ожидание, а не сигнал. Но главное - такая конструкция не будет работать корректно. Пожуем
Код:
    while (!done)               // while (hasSomethingToDo())
        doWork();
   <-- пробой
   emit workFinished();
Допустим done = true, и рабочая нитка приступает к испусканию workFinished. В этот момент главная пуляет следующую задачу, в итоге главная получит сигнал когда последние задачи возможно еще выполняются в рабочей. Кстати защита мутексом здесь ничего не дает.

И снова - не вижу откуда взять "done" не связываясь с (хлопотливой) очередью.


Название: Re: waitForDone
Отправлено: ssoft от Июль 05, 2021, 09:21
Строго говоря, аналогом QThreadPool::waitForDone является QThread::wait.
Здесь же требуется дождаться выполнения всех задач в потоке.

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

Код
C++ (Qt)
thread->eventDispatcher()->processEvents( QEventLoop::AllEvents )
 


Название: Re: waitForDone
Отправлено: RedDog от Июль 05, 2021, 09:28
Опять-таки, требуется (синхронное) ожидание, а не сигнал.
std::async

PS: ожидание асинхронной задачи с остановкой потока - проблемы в архитектуре (мое имхо)


Название: Re: waitForDone
Отправлено: Racheengel от Июль 05, 2021, 13:49
PS: ожидание асинхронной задачи с остановкой потока - проблемы в архитектуре (мое имхо)

Вот у меня тоже закрались подобные подозрения.
Зачем вообще главному потоку знать, что воркеру "делать нечего", в чем суть задачи? Балансирование загрузки? Так нет, судя по всему, воркер только один.
Пусть главный поток сам следит о том, что он на обратку отдал. Воркер почему этим заниматься то должен??


Название: Re: waitForDone
Отправлено: Igors от Июль 05, 2021, 18:06
Строго говоря, аналогом QThreadPool::waitForDone является QThread::wait.
Ну никак не "строго", о (затратном) завершении речь не идет. А предложение processEvents - это куча побочных эффектов (лекарство хуже болезни)

std::async
И сразу дустом в рыло :)

PS: ожидание асинхронной задачи с остановкой потока - проблемы в архитектуре (мое имхо)
Вот у меня тоже закрались подобные подозрения.
Зачем вообще главному потоку знать, что воркеру "делать нечего", в чем суть задачи? Балансирование загрузки? Так нет, судя по всему, воркер только один.
Пусть главный поток сам следит о том, что он на обратку отдал. Воркер почему этим заниматься то должен??
Ага, стандартное "плохая задача". Заметим что точно такой же ф-ционал QThreadPool::waitForDone почему-то не вызывает никаких вопросов. Типовое использование
Код
C++ (Qt)
// main thread
for (...) {
 ...
 if (..)
  emit SignalDoSomething(options);  // QThreadPool::addTask(options)
 ..
}
 
waitForDone();
Накидал задач и жду пока все сварится. Считать это "плохой архитектурой" нет оснований. Синхронка не есть "плохо", она менее гибка, но есть масса ситуаций когда нельзя связываться с др событиями пока данная  (крытыческая) часть кода не завершена. Поэтому иногда ожидание неизбежно/необходимо. А сколько рабочих ниток (одна или N) - чисто специфика задачи.

Ну и вообще - неужели это такая страшная задача которой надо изо всех сил избегать? :) Напр есть простецкое решение:

- завести атомарный счетчик. напр m_taskСount. Главная (посылающий) его инкрементирует, рабочая наоборот, декрементирует. Тогда
Код
C++ (Qt)
void waitForDone( void )
{
 while (m_taskCount)
   QThread::msleep(2);
}
 
Кстати как оформить чище, без sleep? Одно время народ увлекался всякими "футурами", может применить? Или то просто была "дань моде"? :)

Потом увидел еще решение. В раннем детстве смотрел фильм "про шпионов", запомнился эпизод
Цитировать
- Вот она прислала письмо, пишет что все норм. Прочитать?

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


Название: Re: waitForDone
Отправлено: Racheengel от Июль 06, 2021, 10:46
Ну, архитектуры тут нету, это чисто вопрос дизайна приложения.
Да, "плохая задача", инфа 99% :)
Поскольку это "главный" поток, подвешивать его в цикле - это моветон.
Задайте себе вопрос: а почему, собственно, он должен ждать?
Что происходит в момент ожидания?
Что видит юзер в этот момент? Прогресс бар или спиннер? В этом случае надо бы события обрабатывать.
Если же так прямо надо подвеситься, то проще всего решается поллингом воркера. Типичное не модное, но рабочее решение "без зауми".


Название: Re: waitForDone
Отправлено: Igors от Июль 06, 2021, 12:53
Если же так прямо надо подвеситься, то проще всего решается поллингом воркера. Типичное не модное, но рабочее решение "без зауми".
"А я шо делаю?" (как говорят в моем регионе). while m_TaskCount и есть поллинг. Но все-таки sleep - это моветон. Где все эти прелести std:: которые якобы "тупо лучше" ? Макс, где Ваш "вариант с футурами"? :)

Кстати, "контроль кода" в Вашей компании пропустит sleep в цикле ?

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


Название: Re: waitForDone
Отправлено: RedDog от Июль 06, 2021, 13:44
С применением std & boost, но принцип будет понятен:

Код:
std::string fullConfig;
std::condition_variable waiter;
auto configHandler = [&waiter](const std::string& configStr, bool)
{
    fullConfig = configStr;
    waiter.notify_all();
};

boost::asio::io_context ctx;
ConfigReaderWorker worker(ctx, settings, configHandler);
std::thread configThread = std::thread(std::bind([&ctx](){ctx.run();}));

std::mutex locker;
std::unique_lock lock(locker);
waiter.wait_for(lock, []{return !fullConfig.empty();});

worker.stop();
ctx.stop();



Название: Re: waitForDone
Отправлено: m_ax от Июль 06, 2021, 15:10
Цитировать
Синхронка не есть "плохо", она менее гибка, но есть масса ситуаций когда нельзя связываться с др событиями пока данная  (крытыческая) часть кода не завершена. Поэтому иногда ожидание неизбежно/необходимо. А сколько рабочих ниток (одна или N) - чисто специфика задачи.
Да, согласен, в некоторых специфичных задачах это может иметь место. Кстати, недавно же обсуждали подобную проблему: http://www.prog.org.ru/topic_32489_0.html (http://www.prog.org.ru/topic_32489_0.html).
Сошлись на том, что thread_pool оптимальное решение) + wrapper_pool)


Название: Re: waitForDone
Отправлено: Racheengel от Июль 06, 2021, 19:03
Ну это рекомендации что даются другим, сам дающий следовать им не будет :) Ну вот хотя бы отмена фоновой операции. Да, выставил флажок abort для воркера, но дождаться обязан, рыпаться с др событиями пока ресурс(ы) захвачен воркером - себе дороже

Почему же, "сам так делал". Именно что диалог показывал, где прогресс бар и кнопка отмены была. В диалоге ждал, пока воркер или отработает, или отвалится по кнопке.
Ну вернее как ждал, это QProgressDialog ждал, но он то модальный.
Нажал юзер кнопку - шлем воркеру отмену и когда отменил, то прячем диалог.


Название: Re: waitForDone
Отправлено: Racheengel от Июль 06, 2021, 19:06
Кстати, "контроль кода" в Вашей компании пропустит sleep в цикле ?

А что не так со слипом в цикле? Слип останавливает поток на некоторое время, ресурсы проца не жрутся.
Не вижу, почему это плохо :)


Название: Re: waitForDone
Отправлено: RedDog от Июль 06, 2021, 19:29
А что не так со слипом в цикле? Слип останавливает поток на некоторое время, ресурсы проца не жрутся.
Не вижу, почему это плохо :)
Потому что поток стоит, а мог бы работать. Да и проц нечего экономить, пусть трудится.


Название: Re: waitForDone
Отправлено: Racheengel от Июль 06, 2021, 23:51
Потому что поток стоит, а мог бы работать. Да и проц нечего экономить, пусть трудится.

Ну поток не грузовик, он когда не работает, дорогу не занимает)
Тем более в это время другие потоки шуруют во всю, если надо.


Название: Re: waitForDone
Отправлено: RedDog от Июль 07, 2021, 09:17
Потому что поток стоит, а мог бы работать. Да и проц нечего экономить, пусть трудится.

Ну поток не грузовик, он когда не работает, дорогу не занимает)
Тем более в это время другие потоки шуруют во всю, если надо.
ОС контексты потоков все равно переключает, ей не важно спит он или нет.


Название: Re: waitForDone
Отправлено: Igors от Июль 07, 2021, 10:59
...в некоторых специфичных задачах это может иметь место.
Опять интересно наблюдать разницу между собственными решениями и раздаваемыми рекомендациями  :) Помнится сами Вы ждали на мешке футур и ни о какой "асинхронке" и не помышляли.

Почему же, "сам так делал". Именно что диалог показывал, где прогресс бар и кнопка отмены была. В диалоге ждал, пока воркер или отработает, или отвалится по кнопке.
Ну вернее как ждал, это QProgressDialog ждал, но он то модальный.
Нажал юзер кнопку - шлем воркеру отмену и когда отменил, то прячем диалог.
Да, в моей практике этот вариант тоже самый популярный. Но разве это "честная" асинхронка? Нет, события выполняются лишь для модального окна (индикатор прогресса). Это модальная задача, а вовсе не фоновая. И это нормально, обычно др выхода просто нет. В фоне хорошо файл качать который самому не нужен (во всяком случае пока). А если обновляются активные, рабочие данные, отображаемые в UI - какой там нафиг "фон", все сразу рухнет

Кстати есои мы уж так идейны/принципиальны - то давайте заодно рассмотрим и асинхронный вариант, для него ведь тоже решений не видно (пока)


Название: Re: waitForDone
Отправлено: RedDog от Июль 07, 2021, 11:19
Кстати есои мы уж так идейны/принципиальны - то давайте заодно рассмотрим и асинхронный вариант, для него ведь тоже решений не видно (пока)
Код:
void MainThreadWorker::onFinishShadowThread()
{
    emit workShadowThread(/* work params */)
}


Название: Re: waitForDone
Отправлено: Igors от Июль 07, 2021, 11:27
А что не так со слипом в цикле? Слип останавливает поток на некоторое время, ресурсы проца не жрутся.
Не вижу, почему это плохо :)
Да, но так мы расписываемся типа "я маленький, сделать нормально не могу, вот сделал кое-как, не бейте". Но мы же не такие, правда?  :)

Если "чисто объективно" то уязвимое место - сколько ms? Очевидно что если потребуется "выжвть все"  то sleep не годится. Словом, неполноценное и малодушное рещение


Название: Re: waitForDone
Отправлено: Igors от Июль 07, 2021, 12:12
С применением std & boost, но принцип будет понятен:
Вот я хочу работать так
Типовое использование
Код
C++ (Qt)
// main thread
for (...) {
 ...
 if (..)
  emit SignalDoSomething(options);  // QThreadPool::addTask(options)
 ..
}
waitForDone();
Накидал задач и жду пока все сварится
Лично мне это кажется разумным и удобным. Наделал сигналов, связал их с воркером, и просто посылаю "по ходу дела", знать не знаю ни о каких очередях, синхронизации и.т.п. Параметры задачи передаю прямо в аргументах сигнала, остальное сделает Qt.

И тут ..
Код:
std::string fullConfig;
std::condition_variable waiter;
auto configHandler = [&waiter](const std::string& configStr, bool)
на меня выливают целое ведро .. "новых технологий" :) И что, нужно срочно бежать все это учить? (включая дуст). То что я юзал уже не годится, устарело ("Qt только для UI"). Или как?  :)

Ну ладно, попробуем разобраться. Опять та же непонятка: как Вы собираетесь "подкидывать задачи по ходу дела"? Я просто сигналами, а Вы? Какой-то fullConfig, откуда его брать - хз.

Вообще, насколько это "адекввтно"? Нужны ли такие средства для решения задачи что выглядит весьма скромно? (waitForDone)


Название: Re: waitForDone
Отправлено: RedDog от Июль 07, 2021, 13:17
Ну ладно, попробуем разобраться. Опять та же непонятка: как Вы собираетесь "подкидывать задачи по ходу дела"? Я просто сигналами, а Вы? Какой-то fullConfig, откуда его брать - хз.

Вообще, насколько это "адекввтно"? Нужны ли такие средства для решения задачи что выглядит весьма скромно? (waitForDone)
Я просто скопипастил кусок своего кода и рабочего проекта. Буст там исключительно для асинхронности (от Qt отказываемся).
Смысл в том, чтобы запустить параллельную задачу, встать на wait_condition и ждать пока в лямбде этот wait_condition не занатифает кто либо из параллельного потока.
Замените бустовый контекст на QThread а лямбду на слот, и получится то же самое.

upd:
нашел еще один проект, с похожей асинхронкой, почистил от всего лишнего, может натолкнет на мысли


Название: Re: waitForDone
Отправлено: Igors от Июль 07, 2021, 13:49
Смысл в том, чтобы запустить параллельную задачу, встать на wait_condition и ждать пока в лямбде этот wait_condition не занатифает кто либо из параллельного потока.
Это понял, но тогда надо засисять доступ к fullConfig (по сути очередь), а делать это обычно неудобно, напр очередь пуста, но последняя задача еще тикает.

Конечно охаять чужой код - много ума не надо, но впечатление что с "современным С++" забот заметно больше - и очередь (аналог), и примитив синхронизации - все то чего хотелось избежать :)

Да, а "послать чистый лист бумаги" - так никто и не допер? :'( Не может быть, наверное это просто "слишком очевидно"  :)


Название: Re: waitForDone
Отправлено: RedDog от Июль 07, 2021, 22:11
Смысл в том, чтобы запустить параллельную задачу, встать на wait_condition и ждать пока в лямбде этот wait_condition не занатифает кто либо из параллельного потока.
Это понял, но тогда надо засисять доступ к fullConfig (по сути очередь), а делать это обычно неудобно, напр очередь пуста, но последняя задача еще тикает.

Конечно охаять чужой код - много ума не надо, но впечатление что с "современным С++" забот заметно больше - и очередь (аналог), и примитив синхронизации - все то чего хотелось избежать :)

Да, а "послать чистый лист бумаги" - так никто и не допер? :'( Не может быть, наверное это просто "слишком очевидно"  :)
вместо условия !fullConfig.empty() можно завести свой булевый флаг в фоновом треде, который будет означать отсутствие активных задач.


Название: Re: waitForDone
Отправлено: Igors от Июль 08, 2021, 05:18
вместо условия !fullConfig.empty() можно завести свой булевый флаг в фоновом треде, который будет означать отсутствие активных задач.
Воркер четко знает что задача завершена (он ее выполнял), но не может поручиться за отсутствие, ведь задачи посылаются главной. Парить атомарный счетчик (++ в главной, -- в воркере) - да, есть такой вариант, неплохой, позволяет работать чисто сигналами.

Второй вариант в духе "анти-велик", использование Qt инструментария
Код
C++ (Qt)
void waitForDone( void )
{
emit SignalEmptyBlank();  // BlockingQueuedConnection
}
Слот пустой, но он получит упр-е после всех предыдущих сигналов, т.е. когда желанный "done" наступил.

Ну и легким движением руки это превращается в асинхронку. Не умаляя заслуг std/boost замечу что потребности в них здесь никакой


Название: Re: waitForDone
Отправлено: RedDog от Июль 08, 2021, 09:45
Следующим этапом может быть use-case когда надо подождать, но какое то конечное время.
С пустым сигналом просто будет deadlock на все приложение если фоновый поток заблокируется. Ну мало ли, из сети что то не пришло, или файла какого нибудь на месте не оказалось.


Название: Re: waitForDone
Отправлено: Igors от Июль 08, 2021, 13:09
Следующим этапом может быть use-case когда надо подождать, но какое то конечное время.
С пустым сигналом просто будет deadlock на все приложение если фоновый поток заблокируется. Ну мало ли, из сети что то не пришло, или файла какого нибудь на месте не оказалось.
Резонный запрос. Я бы делал так
Код
C++ (Qt)
QSemaphore sem;
emit SignalDone(&sem);
sem.tryAcquire(1, timeOut);