Russian Qt Forum

Qt => Вопросы новичков => Тема начата: Eugene K от Декабрь 16, 2019, 23:02



Название: Параллельный поток
Отправлено: Eugene K от Декабрь 16, 2019, 23:02
Есть поток, где идет обработка данных параллельно основному потоку.

При обработке данных должна быть возможность нажимать на кнопки в программе.


Код:
//класс параллельного потока обработки данных
class MyThread : public QThread, public Ui::MainWindow
{
    Q_OBJECT

public:
volatile bool stopped;
MyThread(){ stopped = false; };
~MyThread() {};
char flag1, flag2;
   
      void run()
{
while (!stopped)
{
mutex.lock();
if(flag2==1) { flag2=0; emit s21();}
mutex.unlock();
           }
}

void MyThread::stop()
{
stopped = true;
}
     
signals:
void s21(void);

private:
QMutex mutex;


//конструктор главной формы
frmMain::frmMain() : QWidget(0, Qt::Window)
{
connect(pushButton_8, SIGNAL(clicked()), SLOT(an_t0()));
connect(&thread1, SIGNAL(s21()), SLOT(an_t21()));
}


void frmMain::an_t0()
{
 
    if(lst.at(0).toInt()==21)
    {
    thread1.start(QThread::LowPriority);
    thread1.flag2=1;         
    }


void frmMain::an_t21()
{
//обработка данных



При работе программы параллельный поток обработки данных замораживает окно программы и кнопки невозможно нажимать.

Где я допустил неточность?


Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 16, 2019, 23:32

Где я допустил неточность?


Грубо говоря - везде. Чуть точнее: an_t21() вызывается в _главном_ потоке.
Еще чуть точнее - выкиньте всё нафиг, опишите что вам надо словами сюда и вам скажут как делать правильно.

ЗЫ: никогда, никогда не наследуйте QThread для реализации бизнес-логики, тем более не наследуйте от нее окно.


Название: Re: Параллельный поток
Отправлено: Eugene K от Декабрь 17, 2019, 05:39
Если словами, то на Qt4.1 (Windows) мне надо организовать четыре параллельных потока: первый принимает данные по сети, второй - обрабатывает эти данные, третий - вычисляет и фильтрует обработанные данные, четвертый - передает отфильтрованные данные по сети дальше.

При этом во время действия этих потоков, оператор может нажимать на кнопки в программе, чтобы смотреть другие окна программы или остановить прием, обработку, вычисление и передачу данных.

Т.е. вопрос в том как мне правильно организовать четыре параллельных потока для этого.


Название: Re: Параллельный поток
Отправлено: qate от Декабрь 17, 2019, 08:46
Qt4.1 вышло в 2012 г. - какая причина использовать эту версию ?

> мне надо организовать четыре параллельных потока: первый принимает данные по сети, второй - обрабатывает эти данные, третий - вычисляет и фильтрует обработанные данные, четвертый - передает отфильтрованные данные по сети дальше.

прием и передачу не надо пихать в отдельные потоки - работай по событиям
для второго и третьего используй https://doc.qt.io/qt-5/qtconcurrentrun.html
если так не пойдет - опиши почему ?


Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 17, 2019, 13:22
Если словами, то на Qt4.1 (Windows) мне надо организовать четыре параллельных потока: первый принимает данные по сети, второй - обрабатывает эти данные, третий - вычисляет и фильтрует обработанные данные, четвертый - передает отфильтрованные данные по сети дальше.

При этом во время действия этих потоков, оператор может нажимать на кнопки в программе, чтобы смотреть другие окна программы или остановить прием, обработку, вычисление и передачу данных.

Т.е. вопрос в том как мне правильно организовать четыре параллельных потока для этого.

Откуда требование в 4 потока?
Начните с однопоточного варианта, но с задумкой распараллелить, как сказано выше через QtConcurrent.
Начните с воркера-приемщика, положите туда сокет, сделайте там начальную валидацию данных, когда пришел чанк, достаточный для обработки, вызывайте функцию обработки.
Функция обработки должна быть свободной (или статической функцией), но отдельной от класса-приемщика, принимающая данные и возвращающая обработанные данные. Так ее можно будет легко завернуть в QtConcurrent.
Аналогично, фильтрация - свободная функция, принимает выход от обработчика.
Дальше, сделайте воркер-отправщик, принимающий слотом фильтрованные данные.

Теперь как это все подружить - сперва сделайте всё в главном потоке. Вероятно, будет тормозить обработка - сперва проверьте, что это так (либо через профилировщик, либо QElapsedTimer вставьте). Если тормозит обработка, выносите ее в поток(и) через QtConcurrent - я бы на обработку+фильтрацию делал одну задачу, но, в зависимости от объема данных, можно использовать и MapReduce. Делать отдельно обработку и фильтрацию не надо - у вас и так будет параллельно может исполнятся несколько задач обработки+фильтраци.и Результат от задачи получайте через QFutureWatcher  в том же главном потоке.
Если тормозит прием по сети (хотя вряд ли), можно завести объект QThread (не наследоваться!) и переместить приемщик, отправщик и FutureWatcher туда. Если у вас всё правильно разделено в однопоточном варианте, то проблем добавить тред не возникнет - это дело 10 строк (start, 3*moveToThread, 3*connect(&QThread::finished), quit, stop)


Название: Re: Параллельный поток
Отправлено: Igors от Декабрь 17, 2019, 14:33
А не лучше ли задействовать QThreadPool скармливая ему возникающие задачи?


Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 17, 2019, 14:42
А не лучше ли задействовать QThreadPool скармливая ему возникающие задачи?

конкаррент именно это и делает


Название: Re: Параллельный поток
Отправлено: Igors от Декабрь 17, 2019, 14:52
конкаррент именно это и делает
Он больше озабочен распределением задач, вычисляет размер "пачки" и.т.п., а здесь это не актуально


Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 17, 2019, 14:59
конкаррент именно это и делает
Он больше озабочен распределением задач, вычисляет размер "пачки" и.т.п., а здесь это не актуально

QtConcorrent::run ничего не распределяет и не вычисляет. Он лишь избавляет от секса с голыми указателями, мьютексами и наследованием раннаблов.


Название: Re: Параллельный поток
Отправлено: Igors от Декабрь 17, 2019, 15:50
QtConcorrent::run ничего не распределяет и не вычисляет.
Это не так, он задумывался как аналог tbb или OpenMP с ключом schedule dynamic. Сначала он дает нитке одну задачу и после завершения анализирует время выполнения. Потом нитка может получить уже 2 (или 10) задач или опять одну, там довольно длинная песня, смысл - оптимально распределить нагрузку.


Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 17, 2019, 16:10
Шта? Вы где это в коде (https://code.woboq.org/qt5/qtbase/src/concurrent/qtconcurrentrun.h.html#_ZN12QtConcurrent3runEPFT_vE) увидели?


Название: Re: Параллельный поток
Отправлено: Igors от Декабрь 17, 2019, 17:33
Шта? Вы где это в коде (https://code.woboq.org/qt5/qtbase/src/concurrent/qtconcurrentrun.h.html#_ZN12QtConcurrent3runEPFT_vE) увидели?
Вот "видимая часть" айсберга, см также timeBefore/timeAfter
Код
C++ (Qt)
  ThreadFunctionResult forThreadFunction()
   {
       BlockSizeManagerV2 blockSizeManager(iterationCount);
       ResultReporter<T> resultReporter(this);
 
       for(;;) {
           if (this->isCanceled())
               break;
 
           const int currentBlockSize = blockSizeManager.blockSize();
 
           if (currentIndex.load() >= iterationCount)
               break;
 
           // Atomically reserve a block of iterationCount for this thread.
           const int beginIndex = currentIndex.fetchAndAddRelease(currentBlockSize);
           const int endIndex = qMin(beginIndex + currentBlockSize, iterationCount);
 
           if (beginIndex >= endIndex) {
               // No more work
               break;
           }
 
           this->waitForResume(); // (only waits if the qfuture is paused.)
 
           if (shouldStartThread())
               this->startThread();
 
           const int finalBlockSize = endIndex - beginIndex; // block size adjusted for possible end-of-range
           resultReporter.reserveSpace(finalBlockSize);
 
           // Call user code with the current iteration range.
           blockSizeManager.timeBeforeUser();
           const bool resultsAvailable = this->runIterations(begin, beginIndex, endIndex, resultReporter.getPointer());
           blockSizeManager.timeAfterUser();
 
           if (resultsAvailable)
               resultReporter.reportResults(beginIndex);
 
           // Report progress if progress reporting enabled.
           if (progressReportingEnabled) {
               completed.fetchAndAddAcquire(finalBlockSize);
               this->setProgressValue(this->completed.load());
           }
 
           if (this->shouldThrottleThread())
               return ThrottleThread;
       }
       return ThreadFinished;
   }


Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 17, 2019, 17:59
Шта? Вы где это в коде (https://code.woboq.org/qt5/qtbase/src/concurrent/qtconcurrentrun.h.html#_ZN12QtConcurrent3runEPFT_vE) увидели?
Вот "видимая часть" айсберга, см также timeBefore/timeAfter

Отлично, где в QtConcurrent::run это используется?
Это используется в QtConcurrent::map, ну да и бог с ним, я же написал ровно что и вы - сперва попробовать следать через run() который (https://code.woboq.org/qt5/qtbase/src/concurrent/qtconcurrentrun.h.html#74) тот (https://code.woboq.org/qt5/qtbase/src/concurrent/qtconcurrentstoredfunctioncall.h.html#QtConcurrent::StoredFunctorCall0) же (https://code.woboq.org/qt5/qtbase/src/concurrent/qtconcurrentrunbase.h.html#QtConcurrent::RunFunctionTask) раннабл (https://code.woboq.org/qt5/qtbase/src/concurrent/qtconcurrentrunbase.h.html#QtConcurrent::RunFunctionTaskBase)


Название: Re: Параллельный поток
Отправлено: Eugene K от Декабрь 17, 2019, 23:55
Пробую использовать moveToThread(thread)

но при создании объекта thread=new QThread() компилятор ругается:
cannot allocate an object of type 'QThread'
because the following virtual functions are abstract: error: virtual void QThread::run()



Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 18, 2019, 00:22
Твою ж, у вас 4.1  >:( Точно никак поновее нельзя взять?

Ладно, делать нечего - надо отнаследоваться от треда и в своем run() тупо вызвать exec(), как сделано (https://code.woboq.org/qt5/qtbase/src/corelib/thread/qthread.cpp.html#_ZN7QThread3runEv) в более новых версиях


Название: Re: Параллельный поток
Отправлено: Eugene K от Декабрь 18, 2019, 00:35
В 4.1 есть moveToThread(). его можно как-то использовать? все таки не просто так он должен быть.


Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 18, 2019, 00:50
В 4.1 есть moveToThread(). его можно как-то использовать? все таки не просто так он должен быть.

Да, я же говорю - надо, чтобы нитка крутила event loop. К сожалению, кутешники не сразу придумали как делать (https://doc.qt.io/qt-5/qthread.html#details) грамотно и до 4.4 метод run() был чисто виртуальным, поэтом без наследования было никак (а я уж и запамятовал).
Но фиксится это тривиально - тупо вызываете exec() внутри run() и всё - это ровно то, что делает QThread начиная с 4.4 и поэтому этот "новый" класс наследовать не надо.

В общем случае, наследоваться от треда плохо, потому что туда начинают писать бизнес-логику и размывается граница что где, то есть какой метод в каком потоке зовется. Когда есть разделение на Worker и QThread с exec'ом всё очевидно - методы треда (start, quit) зовутся из главного потока, методы Worker'а зовутся из потока QThread'а. С главным потоком Worker общается через сигнал-слоты.  То есть, есть разделение по данным => не надо явных мьютексов и иных механизмов синхронизации.

То есть вам нужен такой хелпер
Код:
struct Thread: public Thread
{
    explicit Thread(QObject *parent = 0) : QThread(parent) {}
protected:
    void run() { (void)exec(); }
};

А дальше всё как в примере с воркером


Название: Re: Параллельный поток
Отправлено: Eugene K от Декабрь 18, 2019, 02:12
Cделал так:

frmMain.h:

Код:
struct Thread: public QThread
{
    explicit Thread(QObject *parent = 0) : QThread(parent) {}
public:
    void run() { (void)exec(); }
};


class frmMain : public QWidget, public Ui::MainWindow
{
    Q_OBJECT


public :
    frmMain();
    ~frmMain();

    Thread thread1;

}

frmMain.cpp
(главная форма и главное окно):

Код:
frmMain::frmMain() : QWidget(0, Qt::Window)
{
connect(pushButton_6, SIGNAL(clicked()), SLOT(count1()));


this->moveToThread(&thread1);
connect(&thread1, SIGNAL(started()), this, SLOT(countch()));
}


void frmMain::count1()
{
thread1.start();
}


void frmMain::countch()
{
//вывод данных на экран
}

все равно главное окно заморожено





Название: Re: Параллельный поток
Отправлено: qate от Декабрь 18, 2019, 10:02
В 4.1 есть moveToThread(). его можно как-то использовать? все таки не просто так он должен быть.

Но зачем использовать старое и высохшее 4.1 ?
Найдется там еще баг и как с этим жить ?



Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 18, 2019, 12:33
Cделал так:

Вы упорно продолжаете игнорировать совет использовать отдельного воркера :(

Код:
this->moveToThread(&thread1);
Так делать неправильно (хотя это и можно заставить работать, у меня на работе везде так >:(). У вас есть окно, оно создает тред и владеет им (с тз разрушения объектов), но вы его "перемещаете" внутрь треда, то есть как бы теперь тред "владеет" (с логической точки зрения) окном. Окно владеет тредом который "владеет" окном. Рекурсия, однако.
И в этом не было бы ничего плохого, кроме того, что опять не понятно, в каком потоке какой метод должен вызываться (и ваши проблемы это подтверждают).
Вот например, вы переместили окно в тред и теперь все слоты (по идее) должны вызываться в потоке. В том числе count1(), который стартует поток. Но ведь поток еще на запущен! Вопрос - в каком потоке вызовется этот слот (и вызовется ли?). Я не знаю, дебагать неправильно написанный код мне лень.
Еще, возможно, ваше окно вообще никуда не переместилось, потому что виджеты нельзя перемещать в треды (by design они должны жить в главном потоке). В консоли вы должны видеть что-то типа
Цитировать
QObject::moveToThread: Widgets cannot be moved to a new thread


Название: Re: Параллельный поток
Отправлено: Eugene K от Декабрь 18, 2019, 13:35
Авварон, я не игнорирую совет, я не знаю пока как использовать отдельного воркера.
Я и написал сюда, потоиу, что с потоками не знаком как оказалось. Раньше работал через потоки таймеров и другого не надо было.

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

А то примеры из интернета то ошибки выдают, то не работают.

Возможно дело в версии 4.1.
Версию 4.1 использую по старинке, не было времени переходить на новую. Не знал, что будут такие проблемы. На крайний случай придется на 4.8 переходить, т.к. QtConcurrent с 4.7 идет. Но это надо будет адаптировать программу, а написано немало.


Название: Re: Параллельный поток
Отправлено: Igors от Декабрь 18, 2019, 15:06
Вы на меня не тратьте много времени, а то мне уже неудобно, лучше приведите работающий фрагмент создания параллельного потока типа нажал на кнопку - на экране печатается текст с паузами и в это время можно другие кнопки нажимать (как из моего фрагмента выше).
Ну ладно, купили на "неудобно" :) Аттач. Там нет завершения нитки (разберетесь). Старый синтаксис SLOT/SIGNAL оставил, но если что другое не пойдет на древней версии - проблемы Ваши. Заметьте что нитки должны общаться только сигналами, не пытайтесь лезть к виджетам напрямую из воркера - это рухнет. Ну и если "по-взрослому" то, как сказали выше, никакие QThread здесь не нужны, лучше заюзать штатный тул который сам их сделает

Мне надо создать хотя бы один работающий поток для начала, дальше, думаю, разберусь.
Ох уж это "программирование на примерах"  :)


Название: Re: Параллельный поток
Отправлено: qate от Декабрь 18, 2019, 16:08
Версию 4.1 использую по старинке, не было времени переходить на новую.

какая выдержка и терпение !



Название: Re: Параллельный поток
Отправлено: kambala от Декабрь 18, 2019, 16:38
этой версии наверное уже лет 10, если не больше? :)


Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 18, 2019, 16:43
этой версии наверное уже лет 10, если не больше? :)

Больше, я когда пытался вчера по исходникам найти когда же там run перестал быть виртуальным, выяснил что это было в 4.4, а история в гите начинается с 4.5 в 2009м году. То есть 10-11 лет - это 4.5, а то мамонтово еще древнее ;)


Название: Re: Параллельный поток
Отправлено: ViTech от Декабрь 18, 2019, 17:51
этой версии наверное уже лет 10, если не больше? :)

Qt_version_history Qt_4 (https://en.wikipedia.org/wiki/Qt_version_history#Qt_4): 4.1 - 20 December 2005.

Через пару дней паспорт получать может :).


Название: Re: Параллельный поток
Отправлено: Eugene K от Декабрь 18, 2019, 20:22
.


Название: Re: Параллельный поток
Отправлено: Авварон от Декабрь 18, 2019, 20:46
Боже, ну неужели так самому самому допилить. Проверьте вариант в аттаче