Russian Qt Forum

Программирование => Python => Тема начата: sergs от Февраль 01, 2016, 21:15



Название: Выбор подхода для реализации многопоточности
Отправлено: sergs от Февраль 01, 2016, 21:15
Есть приложение — набор инструментов для обработки данных. Каждый инструмент реализует какой-то один алгоритм. Сейчас приложение работает в один поток, т.е. после запуска одного инструмента приложение блокируется: нельзя запустить ещё один инструмент или работать с приложением.

Хочется переделать, чтобы можно было запускать инструменты не поочередно, а одновремененно в отдельных потоках. С многопоточным программированием плотно не сталкивался. Как я понял есть следующие варианты:
  • использовать Python'овские потоки из модуля threading
  • использовать QThread
  • использовать QThreadPool и QRunnable

Если правильно понимаю, для моих целей наиболее подходящим вариантом является использование QThreadPool и QRunnable. Но дело осложняется тем, что в процессе работы инструменты должны сообщать о прогрессе выполнения, выводить отладочные/информационные сообщения и по завершении передавать результат в основное приложение. Также нужна возможность прерывания работы инструмента пользователем.

Но QRunnable не является наследником QObject и соответственно не может принимать/посылать сигналы. Беглый поиск подсказал решение — в QRunnable создавать QObject с необходимыми сигналами и использовать его для взаимодействия.

Собственно вопросы. Что лучше использовать QThread и реализовывать очередь задач вручную или же взять QThreadPool + QRunnable? Насколько правильным является использование QObject внутри QRunnable для отправки сигналов? Может, есть какие-то другие варианты реализации многопоточности?


Название: Re: Выбор подхода для реализации многопоточности
Отправлено: ksk- от Февраль 02, 2016, 07:17
Если будешь использовать Python'овские потоки из модуля "threading", приложение, по факту, по-прежнему будет однопоточным. Вычёркивай.


Название: Re: Выбор подхода для реализации многопоточности
Отправлено: Igors от Февраль 02, 2016, 10:25
..использовать QThread и реализовывать очередь задач вручную..
Есть гораздо более приятный вариант - пусть Qt займется очередью, что она и делает по умолчанию. Запустили нужное число QThread, они по умолчанию вошли в exec (стоят, ждут работы). И просто шмаляете в них сигналами. Тут правда есть одно "но" - если задачи слишком маленькие (ориентировочно время выполнения < 20 ms), то накладные расходы будут значительны (низкий КПД).


Название: Re: Выбор подхода для реализации многопоточности
Отправлено: sergs от Февраль 02, 2016, 15:26
Запустили нужное число QThread, они по умолчанию вошли в exec (стоят, ждут работы). И просто шмаляете в них сигналами.
Возможно, я не очень понятно описал задачу. Дело в том,что доступных алгоритмов не 2-3, а больше 200. Так что «шмалять сигналами» не получится, ведь для этого на каждый алгоритм надо заводить по потоку. Скорее тогда, создавать потоки, и по необходимости запихивать них объекты-worker'ы. Но тут уже надо отслеживать какой поток занят, а какой нет или прибивать поток по завершению и создавать новый.


Название: Re: Выбор подхода для реализации многопоточности
Отправлено: Igors от Февраль 02, 2016, 15:48
Дело в том,что доступных алгоритмов не 2-3, а больше 200. Так что «шмалять сигналами» не получится, ведь для этого на каждый алгоритм надо заводить по потоку.
Чего это? Напр завели 1 сигнал с параметром индекс/тип алгоритма и пуляете его нужному числу QThread


Название: Re: Выбор подхода для реализации многопоточности
Отправлено: Old от Февраль 02, 2016, 17:17
Если правильно понимаю, для моих целей наиболее подходящим вариантом является использование QThreadPool и QRunnable.
Да, это самый лучший вариант для вас.
То что QRunnable не наследник QObject не имеет значение, QRunnable это интерфейс, в который можно завернуть все что угодно.

Код
C++ (Qt)
// Базовый класс для всех алгоритмов
class Algo : public QObject
{
   Q_OBJECT
public:
   Algo() : QObject() {}
 
   virtual void run() = 0;    // метод выполняющий алгоритм
 
signals:
   void progress( int val );
};
 
class BooAlgo : public Algo
{
   Q_OBJECT
public:
   BooAlgo( int param1, float param2 ) : Algo() { ... }
 
   virtual void run()
   {
       for(;;)
       {
           emit progress( 100500 );
       }
   }
};
 
class AlgoRunnable : public QRunnable
{
public:
   AlgoRunnable( Algo *algo ) : m_algo( algo ) { Q_ASSERT( m_algo ); setAutoDelete( true ); }
   virtual void run()
   {
       m_algo->moveToThread( QThread::currentThread() );
       m_algo->run();
   }
 
private:
   Algo    *m_algo;
};
 
// Использовать так
BooAlgo *a = new BooAlgo( 10, 20 );
connect( a, SIGNAL( progress(int) ), ... );
 
QThreadPool::globalInstance()->start( new AlgoRunnable( a ) );
 
 

Писал прямо здесь, поэтому могут быть опечатки.

И вот еще решение: http://www.prog.org.ru/topic_23042_0.html
Там и пул с runnable уходят под капот.


Название: Re: Выбор подхода для реализации многопоточности
Отправлено: sergs от Февраль 02, 2016, 21:23
Да, это самый лучший вариант для вас.
То что QRunnable не наследник QObject не имеет значение, QRunnable это интерфейс, в который можно завернуть все что угодно.
Огромное спасибо за ответ и пример! Очень помогли.
Я немного иначе планировал сделать, а именно в QRunnаble создавать QObject-пустышку исключительно для отравки сигналов. Но ваш вариант выглядит более логичным и удобным. Спасибо ещё раз.

И вот еще решение: http://www.prog.org.ru/topic_23042_0.html
Там и пул с runnable уходят под капот.
Если правильно понял, там используется QConcurrent. К сожалению, в PyQt этой штуки нет, так что от пула и runnable никуда не уйти.


Название: Re: Выбор подхода для реализации многопоточности
Отправлено: sergs от Февраль 02, 2016, 21:52
Old, попробовал реализовать предложенный вами вариант на PyQt. При попытке поместить worker в поток, в консоли пишется
Код:
QObject::moveToThread: Current thread (0x10ec0f0) is not the object's thread (0xdce0f0).
Cannot move to target thread (0x10ec0f0)
Если правильно понимаю, moveToThread может вызыватся только в том же потоке, в котором был создан объект. Из QtAssistant
Цитировать
Warning: This function is not thread-safe; the current thread must be same as the current thread affinity. In other words, this function can only "push" an object from the current thread to another thread, it cannot "pull" an object from any arbitrary thread to the current thread.

Также, если создавать объект, а затем передавать его в runnable, то у нас не будет возможности прервать выполнение runnable (точнее worker'а внутри нее).


Название: Re: Выбор подхода для реализации многопоточности
Отправлено: Old от Февраль 02, 2016, 22:25
Да, с переносом объекта в рабочий поток не получается. Очередное странное ограничение Qt. :(

Как вариант, можно создавать объект алгоритма в run runnable и коннектиться с внешними слотами оттуда. А указатель на объект получатель передавать в конструкторе runnable.
С шаблонным наследником QRunnable могло бы не плохо получиться.


Название: Re: Выбор подхода для реализации многопоточности
Отправлено: rudireg от Июль 13, 2017, 01:11
А если так?
Код
C++ (Qt)
#ifndef WORKER_H
#define WORKER_H
 
#include <QObject>
#include <QRunnable>
 
class Worker : public QObject, public QRunnable
{
   Q_OBJECT
public:
   Worker(QObject* parent = NULL);
};
 
#endif // WORKER_H