Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: voltron от Апрель 19, 2011, 14:34



Название: Многократный вызов функции из потока
Отправлено: voltron от Апрель 19, 2011, 14:34
Есть некая функция, выполняющая длительную операцию. Хочу вынести ее в отдельный поток, чтобы на время выполнения не блокировался интерфейс. Т.к. эта функция вызывается несколько раз с разными входными параметрами, то хотелось бы реализовать все так, чтобы можно было создать поток один раз и вызывать функцию по необходимости (в остальное время поток должен спать).

Посмотрел в Qt Assistant, похожий функционал есть в Mandelbrot Example. Попробовал реализовать и вот что получилось

extractorthread.h
Код:
class ExtractorThread : public QThread
{
    Q_OBJECT
  public:
    ExtractorThread( QObject *parent = 0 );
    ~ExtractorThread();
   
    // запускает операцию
    void extractData( vectorData* inLayer, vectorData* outLayer, double* transform );
    // для отмены операции
    void stop();
 
  signals:
    void pointProcessed();          // посылается на каждой итерации для отображения прогресса
    void extractionFinished();      // посылается при успешном завершениии
    void extractionInterrupted(); // посылается, если выполнение прервано пользователем
 
  protected:
    void run();
 
  private:
    bool mStop;
    bool mStart;
    QMutex mMutex;
    QWaitCondition mCondition;

    double* mTransform;
    vectorData* mInputData;
    vectorData* mOutputData;
}

extractorthread.cpp
Код:
ExtractorThread::ExtractorThread( QWidget* parent = 0 )
    : QThread( parent ), mStop( false ), mStart( false )
{
}

ExtractorThread::~ExtractorThread()
{
  mMutex.lock();
  mStop = true;
  mCondition.wakeOne();
  mMutex.unlock();
 
  wait();
}

ExtractorThread::extractData( vectorData* inLayer,   vectorData* outLayer, double* transform )
{
  QMutexLocker locker( &mMutex );
 
  mInputData = inLayer;
  mOutputData = outLayer;
  mTransform = transform;
 
  if ( !isRunning() )
  {
    start();
  }
  else
  {
    mStart = true;
    mCondition.wakeOne();
  }
}

ExtractorThread::stop()
{
  mMutex.lock();
  mStop = true;
  mMutex.unlock();
 
  wait();
}

ExtractorThread::run()
{
  mMutex.lock();
  mStop = false;
  mMutex.unlock();
 
  // продготовка, объявление и инициализация локальны переменных 
  .....
 
  bool s = false;
  bool interrupted = false;

  // основной цикл
  while ( mInputData->next() )
  {
    // выполняем обработку
    .....
    emit( pointProcessed() );
   
    // если пользователь решил прервать операцию
    mMutex.lock();
    s = mStop;
    mMutex.unlock();
       
    if ( s )
    {
      interrupted = true;
      break;
    }
  }
 
  if ( !interrupted )
  {
    emit( extractionFinished() );
  }
  else
  {
    emit( extractionInterrupted() );
  }

  mMutex.lock();
  if ( !mStart )
  {
    mCondition.wait( &mMutex );
  }
  restart = false;
  mMutex.unlock();
}

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

И еще один вопрос. В функцию передаются указатели на классы-наследники QObject. Нужно ли на время доступа к методам этих классов защищать их мьютексом?


Название: Re: Многократный вызов функции из потока
Отправлено: merke от Апрель 19, 2011, 14:54
В общем тебе нужно следующее.

Создать не функцию в потоке, а слот с необходимыми параметрами.
А у себя уже создай сигнал с такими же параметрами. Свой сигнал свяжи со слотом из потока.
Когда необходимо передать данные потоку емить свой сигнал.  
Аналогичным способом получай данные из потока. Там емить сигнал с результирующими данными. В главном потоке связывай тот сигнал со своим слотом в главном потоке и обрабатывай данные.

Из этого всего следует, что ни когда не вызывай напрямую функцию в потоке.

Теперь ещё: когда будешь создавать объект своего класса:

Код:
ExtractorThread *myThread = new ExtractorThread();

Делай
Код:
myThread->moveToThread(myThread);


Название: Re: Многократный вызов функции из потока
Отправлено: mutineer от Апрель 19, 2011, 15:10
Теперь ещё: когда будешь создавать объект своего класса:

Код:
ExtractorThread *myThread = new ExtractorThread();

Делай
Код:
myThread->moveToThread(myThread);

Не стоит мувать тред в самого себя. Уж лучше создать свой класс наследником QObject и перемещать его в обычный QThread


Название: Re: Многократный вызов функции из потока
Отправлено: Igors от Апрель 19, 2011, 15:14
Есть некая функция, выполняющая длительную операцию. Хочу вынести ее в отдельный поток, чтобы на время выполнения не блокировался интерфейс. Т.к. эта функция вызывается несколько раз с разными входными параметрами, то хотелось бы реализовать все так, чтобы можно было создать поток один раз и вызывать функцию по необходимости (в остальное время поток должен спать).

Посмотрел в Qt Assistant, похожий функционал есть в Mandelbrot Example. Попробовал реализовать и вот что получилось
Здесь не видно никакой необходимости что-то защищать (конфликтов нет), проще сделать как сказал Александр (с учетом moveToThread что многократно здесь обсуждалось). Если главная нитка хочет прервать вычисления, достаточно если она просто взведет mStop. Это также не требует мутексов, просто надо иметь ввиду: рабочая нитка не прервется немедленно, она должна увидеть mStop = true, закончить вычисления и отэмитить сигнал главной

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


Название: Re: Многократный вызов функции из потока
Отправлено: voltron от Апрель 19, 2011, 17:13
Создать не функцию в потоке, а слот с необходимыми параметрами.
А у себя уже создай сигнал с такими же параметрами. Свой сигнал свяжи со слотом из потока.
Когда необходимо передать данные потоку емить свой сигнал.  
Аналогичным способом получай данные из потока. Там емить сигнал с результирующими данными. В главном потоке связывай тот сигнал со своим слотом в главном потоке и обрабатывай данные.
Спасибо. Значит у меня должно быть что-то вроде такого, да?
Код:
class ExtractorThread : public QThread
{
    Q_OBJECT
  public:
    ExtractorThread( QObject *parent = 0 );
    ~ExtractorThread();
   
  public slots:
    // запускает операцию
    void extractData( vectorData* inLayer, vectorData* outLayer, double* transform );
    // для отмены операции
    void stop();
И соответсвующие сигналы в основной нитке.

Данные назад в основную нитку передаются косвенным образом, примерно так
Код:
  vectorProvider* provider = outLayer->provider();
  QList<Blocks> data;
  provider->addData( data );
Поэтому сигнал с результирующими данными не нужен, нужно только сигнализировать о завершении или прерывании обработки.

Из этого всего следует, что ни когда не вызывай напрямую функцию в потоке.
Т.е. пример Mandelbrot Example из Assistant, где есть вызов функции из потока не совсем правильный и так лучше не делать?


Название: Re: Многократный вызов функции из потока
Отправлено: Igors от Апрель 19, 2011, 17:40
Код:
  public slots: 
    // запускает операцию
    void extractData( vectorData* inLayer, vectorData* outLayer, double* transform );
    // для отмены операции
    void stop();
И соответсвующие сигналы в основной нитке.
Если Вы сделаете stop через сигнал/слот, то он может быть принят рабочей ниткой только после предыдущего (extractData), т.е. такой stop ничего не прерывает. Поэтому stop надо делать напрямую

Из этого всего следует, что ни когда не вызывай напрямую функцию в потоке.
Т.е. пример Mandelbrot Example из Assistant, где есть вызов функции из потока не совсем правильный и так лучше не делать?
Ну "исключения подтверждают правило"  :)


Название: Re: Многократный вызов функции из потока
Отправлено: CL0NE от Апрель 19, 2011, 20:18
Цитировать
Если Вы сделаете stop через сигнал/слот, то он может быть принят рабочей ниткой только после предыдущего (extractData), т.е. такой stop ничего не прерывает. Поэтому stop надо делать напрямую
Дополнение: processEvents/свой eventloop в extractData, тогда можно и слотом