Russian Qt Forum
Ноябрь 25, 2024, 16:05 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Выбор подхода для реализации многопоточности  (Прочитано 12122 раз)
sergs
Гость
« : Февраль 01, 2016, 21:15 »

Есть приложение — набор инструментов для обработки данных. Каждый инструмент реализует какой-то один алгоритм. Сейчас приложение работает в один поток, т.е. после запуска одного инструмента приложение блокируется: нельзя запустить ещё один инструмент или работать с приложением.

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

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

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

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

Сообщений: 178



Просмотр профиля
« Ответ #1 : Февраль 02, 2016, 07:17 »

Если будешь использовать Python'овские потоки из модуля "threading", приложение, по факту, по-прежнему будет однопоточным. Вычёркивай.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #2 : Февраль 02, 2016, 10:25 »

..использовать QThread и реализовывать очередь задач вручную..
Есть гораздо более приятный вариант - пусть Qt займется очередью, что она и делает по умолчанию. Запустили нужное число QThread, они по умолчанию вошли в exec (стоят, ждут работы). И просто шмаляете в них сигналами. Тут правда есть одно "но" - если задачи слишком маленькие (ориентировочно время выполнения < 20 ms), то накладные расходы будут значительны (низкий КПД).
Записан
sergs
Гость
« Ответ #3 : Февраль 02, 2016, 15:26 »

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

Сообщений: 11445


Просмотр профиля
« Ответ #4 : Февраль 02, 2016, 15:48 »

Дело в том,что доступных алгоритмов не 2-3, а больше 200. Так что «шмалять сигналами» не получится, ведь для этого на каждый алгоритм надо заводить по потоку.
Чего это? Напр завели 1 сигнал с параметром индекс/тип алгоритма и пуляете его нужному числу QThread
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #5 : Февраль 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 уходят под капот.
« Последнее редактирование: Февраль 02, 2016, 18:08 от Old » Записан
sergs
Гость
« Ответ #6 : Февраль 02, 2016, 21:23 »

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

И вот еще решение: http://www.prog.org.ru/topic_23042_0.html
Там и пул с runnable уходят под капот.
Если правильно понял, там используется QConcurrent. К сожалению, в PyQt этой штуки нет, так что от пула и runnable никуда не уйти.
Записан
sergs
Гость
« Ответ #7 : Февраль 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'а внутри нее).
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #8 : Февраль 02, 2016, 22:25 »

Да, с переносом объекта в рабочий поток не получается. Очередное странное ограничение Qt. Грустный

Как вариант, можно создавать объект алгоритма в run runnable и коннектиться с внешними слотами оттуда. А указатель на объект получатель передавать в конструкторе runnable.
С шаблонным наследником QRunnable могло бы не плохо получиться.
« Последнее редактирование: Февраль 02, 2016, 22:38 от Old » Записан
rudireg
Гость
« Ответ #9 : Июль 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
 
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.148 секунд. Запросов: 23.