Qt Framework: потоки, иерархический конечный автомат, работа с USB-устройствами = QThread + QStateMashine + libUSB

Почти все разработчики программного обеспечения рано или поздно подходят к этапу, когда необходимо применить технологию распределения задач по отдельным потокам. Сейчас трудно представить разработку без применения того или иного фреймворка (framework).
Множество из них содержат необходимые инструменты для создания многопоточных приложений. Не исключение и Qt Framework.

Поговорим о методах Qt многопоточной разработки подробнее.

Сообщество разработчиков ПО на Qt Framework огромно. Люди охотно делятся навыками и приёмами создания многопоточных приложений. Существует множество замечательных книг и статей по вопросам изящного и не очень решения задачи работы с несколькими потоками.
Казалось бы, всё уже решено. Что ещё можно добавить?
Попробую описать работу потока на основе функционирования конечного автомата. Признаюсь, не находил материалы с подобным решением в сети.
Если статья поможет вам с идеей, что, по моему мнению, гораздо ценнее написанного кода, буду очень рад.

Выражаю отдельную благодарность А.Н. Боровскому за книгу «Qt4.7+.Практическое программирование на C++».
Рекомендую к обязательному прочтению!

Мне так удобно.

В своих кодах я определяю несколько макросов, которые могут показаться избыточными:
#define IS_ZERRO(VALUE)   (0 == (VALUE))
#define MORE_ZERRO(VALUE) (0 < (VALUE))
#define LESS_ZERRO(VALUE) (0 > (VALUE))
#define NOT_ZERRO(VALUE)  (!IS_ZERRO(VALUE))

#define IS_NULL(PTR)      (Q_NULLPTR == (PTR))
#define NOT_NULL(PTR)     (!IS_NULL(PTR))

#define BETWEEN(VALUE,LOW,HIGH) ( ((VALUE) > (LOW)) && ((VALUE) < (HIGH)) )
#define BETWEEN_L(VALUE,LOW,HIGH) (((VALUE) >= (LOW)) && ((VALUE) < (HIGH)))
#define BETWEEN_H(VALUE,LOW,HIGH) (((VALUE) > (LOW)) && ((VALUE) <= (HIGH)))
#define BETWEEN_ALL(VALUE,LOW,HIGH) (((VALUE) >= (LOW)) && ((VALUE) <= (HIGH)))

#define EQUAL(VALUE,EXPR) ((VALUE) == (EXPR))
#define NOT_EQUAL(VALUE,EXPR) (!EQUAL(VALUE,EXPR))


Не буду спорить о целесообразности их применения. Мне кажется, мнемоника не перегружает код и содержит, скажем, «хороший тон» для проверки указателей.
Просто, мне так удобно.

Начнём.

Шаг 1. Немного о потоках «на пальцах».


Если вы правильно поставили ударение в последнем слове, продолжим. :)
Как известно, модель потоков Qt состоит из двух частей:

  • Главный поток приложения;
  • Воспомогательные потоки.


Реализуя объекты-наследники класса QThread (рус.), мы имеем дело с созданием очередных воспомогательных потоков.
Каждые объект класса QThread содержит собственный цикл обработки событий. Цикл запускается защищённым методом QThread: exec (). Запуск потока на выполнение производится вызовом метода QThread: start (). Метод старт запускает на выполнение защищённый метод QThread: run (). В свою очередь, метод run () запускает exec () и, соответственно, собственный обработчик событий для объекта класса QThread.
Метод run () выполняется в адресном пространстве отдельного (назовём его, рабочим) потока. Адресное пространство рабочего потока отличается от адресного пространства объекта класса QThread.

Как реализуется рабочий поток?
Для этого, например, в UNIX-подобных системах используется библиотека pthread. В сети достаточно много материалов о ней.
Метод QThread: start () создаёт отдельный рабочий поток вызовом pthread_create (), передавая ему в качестве функции потока метод QThreadPrivate: start (), а в качестве последнего параметра arg указатель на объект-себя.

Интересен код внутри метода

QThreadPrivate::start ()
{
    . . .
    QThreadData *data = QThreadData::get2(thr);
    . . .
}

Отследив цепочку связей, можно понять, что все объекты, перемещаемые методом QObject: moveToThread (QObject*) будут помещены именно в этот пул данных класса QThreadData адресного пространства рабочего потока.
Немало дискуссий в сообществе разработчиков Qt ведутся на тему, что же лучше: перемещать объекты методом QObject: moveToThread () или перегружать защищённый метод QThread: run ()?
Переопределив метод QThread: run (), знайте, что все объекты, созданные в нем, будут недоступны из вне, т.к. созданы в стеке метода run () (хотя и это можно обойти).
Перемещаемые же объекты хранятся в пуле данных и тянут за собой все свои QMetaObject: Connection-ы.

Помните об этом, и всё будет в порядке.

Упрощённую схему всего вышесказанного можно представить рисунком
828b0f2220b34de88e8e30acf3e9a095.png

Немного о целесообразности использования QThread.


Помимо QThread, Qt предоставляет разработчику набор классов более «высокого» уровня.
Они расположены в пространстве имен QtConcurrent. Классы данного интерфейса полностью берут на себя управление потоком, оптимизируют работу и распределяют нагрузку между потоками. Вы можете набросать в них методов и не заботиться о корректном завершении и очистке памяти. Но есть издержки. Например, класс QRunnable не является потомком QObject, значит, не может отправлять или получать сигналы и не может обрабатывать события.
Думаю, они хорошо подходят для простых методов, например, каких либо математических вычислений, где существует определённый заранее набор входных данных и строгий алгоритм обработки.
Часто приходится создавать постоянные циклы ожидания чего-либо или постоянного сканирования и проверки различных условий. Разработчик помещает их в отдельные потоки для того, чтобы основной цикл обработки события или GUI приложения не «тормозили». Например, используя функции библиотеки libUSB, можно постоянно обновлять список подключённых устройств. В данном случае может оказаться несколько возможных точек выхода из потока как реакция на внешние события, сигналы или возникновения исключительных ситуаций. Главное — мы должны управлять алгоритмом, заключённым в цикл рабочего потока. Для этого, думаю, применение объекта класса производного от класса QThread наиболее оправдано.

Замечание.


Довольно часто в сети встречается некий «финт ушами» по перемещению объекта потока в рабочий поток, а именно

MyThread:: MyThread () : QThread()
{    moveToThread(this);  }


Не делайте этого! Хотя бы потому, что нарушается основная концепция класса QThread как надстройки по управлению потоками. Это похоже на то, что вы пришли на рыбалку, забросили удочку, да и саму удочку тоже забросили в реку…

Создавая объекты потока, вы, наверное, заметили, что значение указателя на объект, перемещённый методом QObject: moveToThread () в адресное пространство рабочего потока, корректно отрабатывает ссылку вне потока. Мы спокойно можем использовать объект по, так сказать, перемещённому указателю для связей QMetaObject: Connection.
Казалось бы, переместили в другое адресное пространство, а, получается так, что никакого перемещения и не было: адрес объекта по значению указателя не изменился.
Рассмотрим одно из отличий процесса и потока. Каждому процессу ОС выделяет собственный пул памяти, но каждый новый поток использует общую память с родительским потоком и процессом, его создавшим. Значит, всё в норме — адрес перемещаемого объекта из потока в поток не меняется относительно основного процесса.
Можно сказать, указатель на объект относительно адресных пространств потоков, чем-то сродни «умному» указателю типа std: auto_ptr, закрепляющему объект за владельцем.

Приведу лирическую аналогию.
//На планете Земля (основной процесс main()) некая древняя цивилизация основала город Кафа 
// с вполне определёнными географическими координатами.
City/*::QObject*/* city = City();
city->setObjectName("Кафа”);
std::out << qPrintable(city) << std::endl;
. . .
//Через века цивилизация утратила своё влияние на город, и город стал принадлежать Крымскому ханству,
QThread* Girey = new QThread(); 
city->setParent(Q_NULLPTR); 
city->moveToThread(Girey);

//  но географические координаты города не изменились.
std::out << qPrintable(city) << std::endl;
. . .
// Что бы ни происходило 
city->setParent(Q_NULLPTR); 
city->moveToThread(RussionImperia); 
. . . 
city->setParent(Q_NULLPTR); 
city->moveToThread(USSR);
. . .
city->setObjectName("Феодосия”);

// и т.д. 


Географические координата города (city) относительно Земли (main ()) не меняются, но инфраструктура (*city) изменчива.


На время приостановим беглое описание класса QThread и перейдём к описанию полезного трюка по корректному завершению потока из по достижении последним любой точки выхода, включая реакцию на возможные исключительные ситуации.

Шаг 2. Корректное завершение потока.


Как известно, потоки могут обмениваться информацией, использовать общие данные, анализировать состояние этих данных и отправлять друг другу различные уведомления.
Один поток косвенно или напрямую может попросить другой приостановить на время работу или, вообще, прекратить своё существование.
Не буду описывать все возможные методы и варианты корректного разделения доступа к данным: материала по данной тематике более чем достаточно. Просто, представим ситуацию, что мы не знаем заранее, когда произойдёт выход из потока, и по какому условию это случиться. Конечно, точки выхода из потока должны срабатывать по завершению некого конечного участка работы потока.
Может возникнуть ситуация, когда сам алгоритм вызовет исключение. Например, обращение к указателю с несуществующим значением. Часто это приводит к полному зависанию потока. Даже метод QThread: terminate () не сможет удалить его из пула выполнения. Так появляются зомби-потоки. В случае с процессами ОС сама попытается уничтожить процесс, но с потоками этот номер не пройдёт, т.к. за их жизнь отвечает основной поток, а не ядро ОС.
Одним словом, мы будем считать, что заранее не знаем полное количество и места точек выхода из потока и код завершения.
Здесь нужен некий помощник — обверка «helper» вокруг объекта потока. Подобные трюки встречаются часто в Qt.

Приведу пример кода и коротко поясню:
Определён класс потока FWThread:

class FWThread : public QThread 
{
}


Определён класс помощника

class FWThread_helper
{
  QPointer m_ext_thread;
Public:
    FWThread_helper(FWThread* aThread)
      : m_ext_thread(aThread)
    {
      Q_ASSERT(NOT_NULL(m_ext_thread));
    }

    ~FWThread_helper()
    {
      if(!m_ext_thread.isNull())
        m_ext_thread->deleteLater();
    }
}


Определим деструктор класса FWThread

FWThread::~FWThread()
{
  quit ();
  if(!wait (FW_THREAD_WAIT_TIMEOUT))
  {
    // ну, очень нехороший случай...
    terminate ();
    if(!wait(FW_THREAD_WAIT_TIMEOUT))
      exit(EXIT_FAILURE);
  }
}


Переопределён метод QThread: run ()

void
FWThread::run()
{
  FWThread_helper* v_helper = new FWThread_helper(this);

//алгоритм работы потока

  m_helper.clear ();
}


Что произойдёт, например, при возникновении исключительной ситуации в теле метода run ()? Что бы ни случилось, указатель v_helper всегда вызовет собственный деструктор либо по m_helper.clear (), либо по очистке стека вызовов метода run () при возникновении исключения.
Деструктор уничтожит объект потока FWThread. По завершению последнего произойдёт попытка выхода из потока или ваш собственный некий алгоритм закрытия потока. Например, все объекты переданные потоку, можно разрушить методом QObject: deleteLater ().

Обратите внимание на строку

Q_ASSERT(NOT_NULL(m_ext_thread));


в теле конструктора класса FWThread_helper. Класс создан специально для непустого указателя на поток. Данный код информирует разработчика об ошибке уаправления потоком на этапе отладки.

Попробуйте определить

void
FWThread::run()
{
  FWThread_helper* v_helper = new FWThread_helper(this);
  QException e;
  
  QT_TRY 
  {
    e.raise();
  } QT_CATCH(QException& e) {
    qDebug() << qPrintable(e.what ());
  }
  m_helper.clear ();
}


Надеюсь, здесь всё понятно.

Шаг 3. Применение иерархического конечного автомата.


Одна из полезнейших, на мой взгляд, реализаций в Qt Framework представлена разделом иерархического конечного автомата (далее КА). КА реализован и в библиотеке boost, но речь пойдёт о Qt.

Ознакомиться с теорией Qt КА можно на странице документации Qt или сайта crossplatform.ru
Каким же образом и, главное, для чего связывать КА и поток?
Немного забегая вперёд, приведу пример используемого мною кода перегрузки метода QThread: run ():

void
FWThread::run()
{
  FWThread_helper* v_helper = new FWThread_helper(this);
  m_helper = v_helper;

  InternalRun ();

  if(IsValid ())
  {
    BeforeMachineStart ();

    /// Запуск детерминированного Конечного Автомата @sa Machine ().
    Machine ()->start ();

    /// Запуск циклa обработки событий потока. => Начало работы КА.
    BaseClass::exec ();
  }
  else
  {
    if(!IsError ()) SetError ();
    exit (FW_ERR_THREAD_INCOMPLETE);
  }

  m_helper.clear ();
}


Во всех классах, образованных от класса FWThread (:: QThread), я переопределяю лишь единственный собственноручно добавленный метод FWThread: BeforeMachineStart (). Никакой метод непосредственной работы с потоком больше не перегружается!
Каждый метод BeforeMachineStart () нового класса наследника FWThreadдобавляет состояния КА и переходы между ними. Таким образом, происходит локализация изменения алгоритма метода QThread: run () лишь в одном файле реализации класса, а сам поток может обслуживать различные модели поведения конечного автомата. Причём, при остановке потока можно запретить QObject: deleteLater () для объекта класса FWThread, динамически изменить поведение КА и запустить тот же самый экземпляр потока методом FWThread: start () на выполнение заново с совершенно другой моделью поведения КА!

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

В реализации Qt конечный автомат представлен классом QStateMachine (рус.), состояния описаны классами QState (рус.), QHistoryState (рус.), QFinalState (рус.). Переходы представлены классами QEventTransition (рус.) и QSignalTransition (рус.).
Все перечисленные классы основаны на множестве абстрактных классов. Так QState расширяет абстрактный класс QAbstractState (рус.). Класс КА QStateMachine — производная от QState. Классы переходов ведут начало от класса QAbstractTransition (рус.).
Документация по Qt содержит весьма подробное описание КА, классов состояний и переходов, достаточное множество простых примеров и трюков при проектировании и реализации КА.
Существует отличный и полный перевод описания КА Qt на русском языке
Прочтите. Это, просто, интересно.

Из примеров и рекомендаций документации Qt для работы потока на основе КА я выбрал схему, аналогичную рисунку примера документации.
9cb87f06862e4c2ebf2672fec76e86ae.png

Переопределяя классы автомата, состояний и переходов, я на некоторое время отказался от использования класса QSignalTransition. Любой сигнал можно продублировать как посыл события обработчику события потока.
Представьте картину, когда у вас объект приёмника событий спрятан как атрибут некого класса. Объект последнего так же является атрибутом какого-либо класса и т.д. Чтобы оттранслировать сигнал, разработчику придётся не забыть о переназначении связей метода QObject: connect () вглубь объекта, пока сигнал не достигнет цели. И наоборот, ретранслировать сигналы глубоко вложенных объектов как атрибутов класса на верхний уровень.
Куда проще определить собственный формат сообщений на основе класса QEvent c некими дополнительными атрибутами и использовать метод QStateMachine: postEvent () или QCoreApplication: postEvent ().

Дополнительно, я использую собственную модель классов, основнных на классе QObject.
Так, базовый класс у меня звучит как class FWIterm: public QObject.
Объявляя новые классы, я наследую их только от класса FWItem. Это позволяет ввести некие флаги и признаки, общие для всех. К тому же, наследники FWItem могут дополнять и расширять наборы флагов, множеств и прочих атрибутов типов класса FWItem.

Самое главное то, что базовый класс FWItem содержит методы назначения, проверки и ссылки на конечный автомат:
class FW_LIBRARY FWItem : public QObject
{
  private:
    QPointer m_machine;

  protected:
    /**
     * @brief Метод MachineExists проверяет наличие в объекте класса конечного
     * автомата класса @sa FWStateMachine.
     * @return Булево значение:
     * @value TRUE Автомат присутствует в объекте класса;
     * @value FALSE Конечный автомат не используется.
     */
    virtual bool
    MachineExists () const { return !m_machine.isNull (); }

    /**
     * @brief Метод Machine возвращает указатель на встроенный детерминированный
     * конечный автомат (КА) типа @sa FWStateMachine.
     * @return Указатель на встроенный КА.
     *
     * Класс @sa FWStateMachine является наследником @sa QStateMachine.
     *
     * Метод предназначен для перегрузки при использовании в классах, производных
     * от @sa FWItem, конечного автомата, созданного на основе классас @sa FWStateMachine.
     */
    virtual FWStateMachine*
    Machine () const;
  public:
    /**
     * @brief Метод SetMachine назначает объекту класса конечный автомат.
     * @param aMachine Указатель на объект КА.
     */
    void
    SetMachine (FWStateMachine* aMachine);
}



Таким образом, при наличии КА для объекта наследника FWItem, можно управлять посылом сообщений для состояний и переходов напрямую:

if(item-> MachineExists())
  Machine()->PostEvent(FWEvent::OEI_Open);


Где FWEvent: OEI_Open некий, определённый идентификатор сообщения класса FWEvent.

Диаграмма состояний и переходов для автомата управления работой потока принимает вид,
763929c048d44a349e782a025e2248bc.png
где T(сигнал типа FWEvent: Event: Type) — переход из одного состояния в другое.

Для простоты понимания переходов между состояниями в класс FWStateMachine добавлен набор виртуальных методов FWStateMachine: AddBranch (<событие или конструктор события><исходное состояние><состояние назначения> == Q_NULLPTR);
Для многих решений задач на основе КА Qt данной схемы и количества состояний вполне достаточно. Поэтому я внедрил её в тело реализации класса FWMachineState, создав защищённый (protected) виртуальный метод Initialisation ().

Рассмотрим его:
void
FWStateMachine::Initialisation()
{
  bool v_active = IsActive ();
  if(v_active)
    stop ();

  /// @name Определение группы состояний КА
  /// @{
  if(!StateGroupExists ())
    m_state_group.reset (new FWState(this));

  FWState* sIdle = new FWState(StateGroup ());
  StateGroup ()->setInitialState (sIdle);
  /// @}

  /// @name Назначение правил работы исторического состояния КА
  /// @{

  if(!StateHistoryExists ())
    // переход с историческим состоянием, в качестве цели, закончится в наиболее
    // глубоко вложенном состоянии-потомке @sa m_state_group (@sa QHistoryState::DeepHistory), 
    // в котором родительское
    // находилось в последний раз, когда из него вышли.
    m_state_history.reset (new QHistoryState(QHistoryState::DeepHistory,StateGroup ()));

  /// @}

  /// @name Определение состояниz завершения работы КА
  /// @{

  if(!StateFinalExists ())
    m_state_final.reset (new QFinalState(this));

  /// @}

  /// @name Определение ошибочного состояния КА
  /// @{

  if(!StateErrorExists ())
    m_state_error.reset (new FWState(this));

  /// @}

  /// @name Назначение переходов для основного состояния КА
  /// @{

  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Stop,
            StateGroup (), StateFinal ());
  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Error,
            StateGroup (), StateError ());

  /// @}

  /// @name Назначение переходов для состояния приостановки работы
  /// @{

  FWState* sPause = new FWState(this);
  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Pause,
            StateGroup (), sPause);
  AddBranch(FWEvent::ET_INTERNAL,FWInternalEvent::OEI_Resume,
            sPause, StateHistory ());

  /// @}

  /// @name Назначение переходов для состояния ошибки
  /// @{
  AddBranch(StateError (), StateFinal ());
  /// @}

  /// @name Назначение обработчиков переходов между состояниями КА
  /// @{

  connect( sPause,&FWState::        entered,
           this,  &FWStateMachine:: slot_OnPaused,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( sPause,&FWState::        exited,
           this,  &FWStateMachine:: slot_OnResume,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( StateFinal (),&QFinalState::    entered,
           this,         &FWStateMachine:: slot_OnStop,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( StateError (),&FWErrorState::   entered,
           this,         &FWStateMachine:: slot_OnError,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  connect( StateHistory(),&QHistoryState::defaultStateChanged,
           this,          &FWStateMachine::slot_OnDefaultHistoryChanged,
           Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  /// @}

  if(NOT_EQUAL(initialState (),StateGroup ()))
    setInitialState (StateGroup ());

  StateGroup ()->   setObjectName("State GROUP");
  StateHistory ()-> setObjectName("State HISTORY");
  StateError()->    setObjectName("State ERROR");
  StateFinal()->    setObjectName("State FINAL");
  sPause->          setObjectName("State PAUSE");
  sIdle->           setObjectName("State IDLE");

  if(v_active) start ();
}


В наследниках класса FWStateMachine

можно перегрузить слоты
  . . .
  protected Q_SLOTS:

    virtual void
    slot_OnLoging ()
    { qDebug() << qPrintable(Q_FUNC_INFO); }

    virtual void
    slot_OnError ()
    { qDebug() << qPrintable(Q_FUNC_INFO); }

    virtual void
    slot_OnPaused ()
    {
      qDebug() << qPrintable(Q_FUNC_INFO);
      if(thread ())
        thread ()->yieldCurrentThread ();
      emit sign_Paused();
    }

    virtual void
    slot_OnResume ()
    {
      qDebug() << qPrintable(Q_FUNC_INFO);
      emit sign_Resumed();
    }

    virtual void
    slot_OnStop ()
    {
      qDebug() << qPrintable(Q_FUNC_INFO);
    }

    virtual void
    slot_OnDefaultHistoryChanged()
    {
      if(StateHistoryExists ())
        if(NOT_NULL(StateHistory()->defaultState()))
          qDebug() << "-H-" << StateHistory()->defaultState()->objectName();
    }
};


Вернёмся к классу FWThread и посмотрим на метод Reconnect (), вызываемый в теле метода run ()

FWThread: Reconnect ()
void
FWThread::Reconnect()
{
  if(IsAutoDeleted ())
    // Уничтожение экземпляра потока после завершения работы
    connect( this, &QThread::finished,
             this, &QThread::deleteLater,
             Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));

  if(MachineExists ())
  {
    // Завершение цикла обработки событий потока при достижении конечным
    // автоматом состояния завершения работы
    connect( Machine (),&FWStateMachine:: finished,
             this,      &QThread::        quit,
             Qt::ConnectionType(Qt::AutoConnection | Qt::UniqueConnection));

    // Завершение работы конечного автомата после согнала потока о
    // завершении работы.
    connect( this,      &QThread::        finished,
             Machine (),&FWStateMachine:: stop,
             Qt::ConnectionType(Qt::AutoConnection | Qt::UniqueConnection));

    /// @name Немедленная ретрансляция сигналов конечного автомата из @sa FWThread
    /// @{
    connect (Machine (),&FWStateMachine:: started,
             this,      &FWThread::       sign_MachineStarted,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

    connect (Machine (),&FWStateMachine:: stopped,
             this,      &FWThread::       sign_MachineStopped,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

    connect (Machine (),&FWStateMachine:: sign_Paused,
             this,      &FWThread::       sign_MachinePaused,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

    connect (Machine (),&FWStateMachine:: sign_Resumed,
             this,      &FWThread::       sign_MachineResumed,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
    /// @}

    // обязать объект потока выполнить некоторые действия немедленно после
    // запуска конечного автомата
    connect (Machine (),&FWStateMachine:: started,
             this,      &FWThread::       slot_AfterMachineStart,
             Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
  }
}



В коде я стараюсь подробно описывать все действия. Привычка помогает минимизировать текст статьи.

Осталось немного: добавить в класс-помощник FWThread_helper для класса FWThread пару полезных деталей и методов. Вы найдёте их, просмотрев прилагающийся листинг.

FWThread.h
///
/// \language Russian
///

#ifndef FW_THREAD_H
#define FW_THREAD_H

#include 

FW_BEGIN_NAMESPACE

//------------------------------------------------------------------------------
/**
 * @brief Внешняя переменная FW_THREAD_WAIT_TIMEOUT содержит значение
 * задержки ожидания для метода @sa QThread::wait.
 *
 * @see QThread
 */
extern unsigned long FW_THREAD_WAIT_TIMEOUT;

//------------------------------------------------------------------------------
class FWThread_helper;
class FWStateMachine;

//------------------------------------------------------------------------------
class
    #ifdef FW_LIBRARY
        FW_SHARED_EXPORT
    #else
        Q_DECL_EXPORT
    #endif
FWThread : public QThread
{
    friend class FWThread_helper;

    Q_OBJECT

    Q_CLASSINFO("brief",          "Framework Thread Class with QStateMashine")
    Q_CLASSINFO("created",        "03-JUN-2015")
    Q_CLASSINFO("modified",       "23-JUN-2015")
    //
    Q_CLASSINFO("project",        "Common Qt-based Framework")
    //
    Q_CLASSINFO("info_ru",        "http://doc.crossplatform.ru/qt/4.7.x/qthread.html#")

//    Q_DISABLE_COPY(FWThread)

    /// @name Локальные типы класса.
    /// @{
    /// приведение типа базового класса для независимости реализации
    typedef QThread BaseClass;
    /// @}

  public:
    enum FWThreadFlag
    {
      THF_Empty       = 0x00,
      THF_Ready       = 0x01,
      THF_Working     = 0x02,
      THF_Paused      = 0x04,
      THF_AutoDelete  = 0x08,
      THF_Error       = 0x80,
    };
    Q_DECLARE_FLAGS(FWThreadFlags, FWThreadFlag)

  private:

    QPointer m_helper;

    ///
    /// @brief Атрибут m_flags представляет флаги состояния экземпляра потока.
    /// @see FWThreadFlags.
    ///
    QAtomicInt   m_flags;

    /// @name Закрытые (служебные) методы работы с флагами класса
    /// @{

    void
    SetEmpty ()
    {
      m_flags.fetchAndStoreOrdered (THF_Empty);
    }

    void
    SetReady ()
    {
      FWThreadFlags v_flags = Flags () | THF_Ready;
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void UnsetReady ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_Ready);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetWorking ()
    {
      FWThreadFlags v_flags = Flags () | THF_Working;
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetStop ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_Working);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetPause ()
    {
      FWThreadFlags v_flags = Flags () | THF_Paused;
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetResume ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_Paused);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetAutoDelete ()
    {
      FWThreadFlags v_flags = Flags () | THF_AutoDelete;
      m_flags.fetchAndStoreOrdered (v_flags);
      Reconnect();
    }

    void
    UnsetAutoDelete ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_AutoDelete);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    SetError ()
    {
      FWThreadFlags v_flags = Flags () | THF_Error;
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    void
    UnsetError ()
    {
      FWThreadFlags v_flags = Flags () & (~THF_Error);
      m_flags.fetchAndStoreOrdered (v_flags);
    }

    /// @}

    /**
     * @brief Метод InternalRun производит подключение правил взаимодействий
     * экземпляра класса и конечного автомата с установкой признака готовности.
     */
    void
    InternalRun();

  public:

    /**
     * @static
     * @brief Метод SetThreaWaitTimeout назначает новое значение
     * задержки ожидания для метода @sa QThread::wait.
     * @param aValue новое значение задержки ожидания для метода @sa QThread::wait.
     *
     * @see QThread
     */
    static void
    SetThreaWaitTimeout( unsigned long aValue)
    {
      if(NOT_EQUAL(FW_THREAD_WAIT_TIMEOUT,aValue))
        FW_THREAD_WAIT_TIMEOUT = aValue;
    }

    /**
     * @brief Конструктор объекта @sa FWThread
     * @param aAutoDelete Признак автоматического разрушения объекта класса по
     * окончании работы потока.
     * @param aParent указатель на объект-родитель класса.
     *
     * @note Если указатель @sa aParent на объект-родитель класса не пуст,
     * значение параметра @sa aAutoDelete игнорируется, и поток не разрушается
     * автоматически после завершения работы потока выполнения.
     */
    explicit
    FWThread(const bool aAutoDelete = true, QObject* aParent = Q_NULLPTR);

    ~FWThread();

    /**
     * @brief Метод AsBaseClass представляеь указатель на экземпляр (оюъект)
     * класса @sa FWThread как указатель на базовый класс @sa QThread.
     * @return указатель типа @sa QThread на экземпляр дапнного класса.
     */
    BaseClass*
    AsBaseClass () { return qobject_cast(this); }

    ///
    /// @brief Метод setParent перегружает базовый метод @sa QObject::setParent.
    /// @param aParent Указатель на объект "родителя"
    /// Метод введён в класс с целью предотвращения вызова автоматического
    /// разрушения объекта класса в случае начальной установки флага @sa aAutoDelete
    /// в значение TRUE.
    /// @see FWThread::FWThread
    ///
    virtual void
    setParent(QObject* aParent);

    /**
     * @brief Метод Flags позволяет получить список флагов экземпляра FWThread.
     * @return Значение флагов потока типа @sa FWThread::FWThreadFlags
     */
    inline FWThreadFlags
    Flags () const { return FWThreadFlags(m_flags.loadAcquire ()); }

    /**
     * @brief Метод Machine возвращает указатель на КА, упроавляющий работой
     * потока.
     * @return указатель на КА текущего экземпляра класса.
     *
     * @warning Конечный автомат выполняется в адресном пространстве рабочего
     * потока, не совпадающем с адресным пространством объекта текущего
     * класса.
     */
    FWStateMachine*
    Machine ();

    /**
     * @brief Метод MachineExists проверяет доступность указатела на КА.
     * @return Булево значение:
     * @value TRUE сообщает о наличии КА;
     * @value FALSE сообщает о том, что экземпляр КА не создан.
     */
    inline bool
    MachineExists () { return NOT_NULL(Machine ()); }

    /**
     * @brief Метод IsValid сообщает об соответствии экземпляра данного класса
     * условиям применения во внешних к объекту класса вычислениях.
     * @return Булево значение:
     * @value TRUE сообщает о выполнении условий валидности экземпляра класса;
     * @value FALSE сообщает о неготовности экземпляра класса к применению.
     */
    virtual bool
    IsValid ();

    /**
     * @brief Метод IsReady информирует о готовности объекта класса к запуску.
     * @return Булево значение:
     * @value TRUE сообщает о готовности объекта потка к запуску;
     * @value FALSE сообщает о неготовности объекта класса к запуску.
     */
    inline bool
    IsReady () const { return Flags ().testFlag (THF_Ready); }

    /**
     * @brief Метод IsAutoDeleted информирует об автоматическом уничтожении
     * экземпляра данного класса после завершения работы основного метода потока.
     * @return Булево значение:
     * @value TRUE сообщает, что объект данного класса будет разрушен автоматически;
     * @value FALSE сообщает, что объект данного класса не будет разрушен автоматически.
     *
     * Например,
     *  @
     *  void foo ()
     *  {
     *    FWTread* v_thread = new FWTread(true);
     *    bool v_with_timeout = <условие>;
     *    ...
     *    thread.quit ();
     *
     *    if(v_with_timeout)
     *    {
     *      if(!thread.wait (FW_THREAD_WAIT_TIMEOUT) )
     *      {
     *      // например,
     *      //  terminate ();
     *      //  wait(1000);,
     *
     *      //  Хотя terminate() не рекомендуется, но наш класс его
     *      //  переопределил как quit() ;)
     *      ...
     *      }
     *    }
     *    else
     *      v_thread->Machine()->stop ();
     *      //Останов конечного автомата вызовет FWTread::quit, FWTread::finished
     *      //вызовет FWTread::deleteLater, произойдёт разрушение v_thread, и
     *      //вызовется деструктор класса @sa FWThread_helper атрибута FWTread::m_helper.
     *  }
     * @
     */
    inline bool
    IsAutoDeleted () const { return Flags ().testFlag (THF_AutoDelete); }

    inline bool
    IsError () const { return Flags ().testFlag (THF_Error); }

    inline bool
    IsWorking () const { return Flags ().testFlag (THF_Working); }

    inline bool
    IsPaused () const { return Flags ().testFlag (THF_Paused); }

    /**
     * @brief Метод AttachObject перемещает объект в адресное пространство
     * потока выполнения.
     * @param aObject Указатель на объект.
     * @return Булево значение:
     * @value TRUE сообщает, что объект перемещён успешно;
     * @value FALSE сообщает, что перемещение объекта вызвало ошибки.
     */
    bool
    AttachObject (QObject* aObject);

    /// @name Перегруженные методы класса @sa QThread
    /// @{

    /**
     * @brief Переопределённый метод класса @sa QThread::terminate.
     * Перед выходом из потока выполнения останавливает КА, если тот активен.
     */
    void terminate();

    /**
     * @brief Переопределённый метод класса @sa QThread::quit.
     * Перед выходом из потока выполнения останавливает КА, если тот активен.
     */
    void quit();

    /// @}

  protected:

    /**
     * @brief Метод Reconnect определяет взаимодействие экземпляра конечного
     * автомата и потока.
     *
     * Посредством привязки сигналов потока и экземпляра КА к слотам потока и КА
     * определяется логика запуска и завершения работы КА. Также порядок
     * разрушения экземпляра КА и экземпляра потока.
     */
    void
    Reconnect ();

    /**
     * @brief Метод run перегружает базовый метод @sa QThread::run.
     * Данный метод инициализирует объект @sa m_helper и начальную настройку
     * класса методом @sa InternalRun
     *
     * До запуска метода цикла обработки событий методом @sa exec() данный метод
     * перенмещает экземпляр КА в адресное пространство потока.
     * Все зависимык от КА объекты, определённые как дочерние, так же автоматически
     * перемещаются в адресное пространство потока.
     * После этого происходит запуск цикла обработки событий методом @sa exec().
     *
     * Метод запускакт Конечный Автомат и цикл обработки событий потока на
     * выполнение.
     * Перед этим вызывается метод @sa BeforeMachineStart.
     *
     * @note Переопределите метод @sa BeforeMachineStart для добавления
     * некоторых состояний и переходов КА.
     *
     * @warning При переопределении не забывайте переместить необходимые объекты
     * в адресное пространство потока выполнения вызовом метода @sa AttachObject.
     *
     * @see QThreadPrivate::start, QThread::start, QThread::exec, QThread::exit.
     */
    void
    run() Q_DECL_OVERRIDE;

  public:

    /**
     * @brief Метод BeforeMachineStart позволяет выполнить некоторые особые
     * действия перед запуском КА и основного алгоритма потока на выполнение.
     *
     * Изначально, КА запрограммирован на работу основных состояний и переходов
     * между ними.
     * Перегрузка данного метода позволяет, скажем, перед запуском КА внести
     * дополнительные состояния КА и переходы между ними или перенаправить
     * существующие.
     *
     * @note Используйте перегрузку данного метода для определения собственных
     * состояний и переходов КА в дополнение к базовым. В классе @sa FWStateMachine
     * определено начальное состояние как группа для возможных пользовательских
     * состояний. Доступ к нему можно получить методом вызова последовательности
     * методов Machine ()->StateGroup (). В самой группе изначально определено
     * собственное начальное состояние, которое можно использовать для начала
     * переходов. Доступ к начальному состоянию группы можно получить вызовом
     * метода Machine ()->StateIdle ().
     *
     * Пример перегрузки метода @sa BeforeMachineStart в классе FWUsbScanner
     * определения подключения/отключения USB-устройств:
     * @
     *
     * @
     *
     * @see FWStateMachine, FWState
     * @see QStateMachine, QState, QAbstractTransition
     */
    virtual void
    BeforeMachineStart () {}

    /**
     * @brief Метод BeforeThreadDone позволят выполнить некоторые особые
     * действия перед завершением работы основного алгоритма потока.
     *
     * Необходимость в таком методе возникает потому, что у процедуры потока
     * есть только одна точка входа, но может быть много точек выхода.
     *
     * Метод @sa BeforeThreadDone() избавляет от необходимости дублировать код.
     *
     * Для внесения специфичных действий необходимо перегрузить данный метод.
     *
     * @note Важно помнить: тело метода будет выполняться в адреадресном
     * пространстве потока, но не в адресном пространстве экземпляра
     * данного класса @sa FWThread!
     *
     * @note Важно помнить: тело метода будет выполняться в адреадресном
     * пространстве потока, но не в адресном пространстве экземпляра
     * данного класса @sa FWThread!
     *
     * Данный метод будет вызван даже в случае возникновения исключительной
     * ситуации в методе @sa FWThread::run.
     */
    virtual void
    BeforeThreadDone() {}

    /**
     * @brief Метод AfterThreadDone позволят выполнить некоторые особые действия
     * после завершения работы основного алгоритма потока.
     *
     * Необходимость в таком методе возникает потому, что у процедуры потока
     * есть только одна точка входа, но может быть много точек выхода.
     *
     * Метод @sa AfterThreadDone, как и метод @sa BeforeThreadDone(), избавляет
     * от необходимости дублировать код.
     *
     * Для внесения специфичных действий необходимо перегрузить данный метод.
     *
     * @note Важно помнить: тело метода будет выполняться, всё еще,в адреадресном
     * пространстве потока, но не в адресном пространстве экземпляра
     * данного класса @sa FWThread!
     *
     * Данный метод будет вызван даже в случае возникновения исключительной
     * ситуации в методе @sa FWThread::run.
     */
    virtual void
    AfterThreadDone() {}

    /**
     * @brief Метод ThreadDone выполняет действия, связанные с завершением
     * работы потока.
     *
     * Этот метод нужно вызывать тогда, когда завершается работа потока без
     * участия @sa IsBreak (например, в конце процедуры потока).
     * Метод @sa IsBreak , при необходимости, вызовет этот метод сам.
     */
    void
    ThreadDone ();

  Q_SIGNALS:

    /**
     * @brief Сигнал sign_MachineChanged оповещает об изменении адреса объекта
     * КА после создания его в потоке или удаления (разрушения).
     * @param aPointer указатель на объект Конечного автомата (КА).
     */
    void
    sign_MachineChanged(const FWStateMachine* aPointer);

    /**
     * @brief Сигнал sign_MachineStarted оповещает о запуске КА на выполнение.
     * @note Является ретрансляцией сигнала @sa QStateMachine::started;
     */
    void
    sign_MachineStarted();

    /**
     * @brief Сигнал sign_MachineStopped оповещает о хавершении работы КА.
     * @note Является ретрансляцией сигнала @sa QStateMachine::finished;
     */
    void
    sign_MachineStopped();

    /**
     * @brief Сигнал sign_MachinePaused оповещает о приостановке работы КА.
     */
    void
    sign_MachinePaused ();

    /**
     * @brief Сигнал sign_MachineResumed оповещает о возобновлении работы КА.
     */
    void
    sign_MachineResumed ();

    /**
     * @brief Сигнал sign_ObjectAddress ретранслирует сигнал @sa FWItem::sign_ObjectAddress
     * от объектов, принадлежащих адресному пространству потока выполнения.
     * @param aMyAddress указатель на объект (значение адреса)
     */
    void
    sign_ObjectAddress (QObject* aMyAddress);

  public Q_SLOTS:

    /**
     * @brief Слот slot_AfterMachineStart позволяет выполнить некоторые действия
     * после запуска КА на выполнение.
     *
     * Действия, описанные данным слотом, выполняться лишь после запуска цикла
     * обработчика события потока методом @sa QThread::exec. Таким образом, метод
     * @sa FWStateMachine::start не запустит данный слот на выполнение до
     * вызова @sa QThread::exec.
     *
     * Перегруженный в данном классе метод QThread::run сперва вызывает метод
     * @sa FWStateMachine::start, а затем и  @sa QThread::exec.
     *
     * @note Важно помнить: тело метода будет выполняться в адреадресном
     * пространстве потока, но не в адресном пространстве экземпляра
     * данного класса @sa FWThread!
     */
    virtual void
    slot_AfterMachineStart () { if(!IsError ()) SetWorking (); }

    /**
     * @brief Слот slot_Pause запускает на выполнение
     * слот @sa FWStateMachine::slot_Pause.
     */
    void
    slot_Pause ();

    /**
     * @brief Слот slot_Resume  запускает на выполнение
     * слот @sa FWStateMachine::slot_Resume
     */
    void
    slot_Resume ();
};

//------------------------------------------------------------------------------

FW_END_NAMESPACE

Q_DECLARE_OPERATORS_FOR_FLAGS(FW_NAMESPACE::FWThread::FWThreadFlags)

#endif // FW_THREAD_H



FWThread.cpp
#include "FWThread.h"
#include "FWStateMachine.h"
#include 
#include 

FW_BEGIN_NAMESPACE

//------------------------------------------------------------------------------
unsigned long FW_THREAD_WAIT_TIMEOUT  = 7000;

//------------------------------------------------------------------------------
/**
 * @brief Класс FWThread_helper представляет воспомогательные действия по
 * корректному завершению рабочего потока класса @sa FQThread.
 *
 * Объект класса формируется в адресном пространстве рабочего потока объекта
 * класса @sa FQThread. Для этого переопределяется метод @sa Qthread::run :
 * @
 * FWThread::run()
 * {
 *    QScopedPointer v_helper(new FWThread_helper(this));
 *
 * }
 * @
 *
 * "Умный" указатель v_helper на объект класса @sa FWThread_helper гарантирует
 * разрушение объекта после завершения выполнения рабочего потока даже при
 * возникновении исключительной ситуации.
 *
 * Класс опознаёт поток-хозяин типа @sa FWThread по содержимому атрибута
 * @sa m_ext_thread.
 *
 * В классе объявлен атрибут @sa m_fsm, представляющий детерминирова
    
            

© Habrahabr.ru