QThread

Автор: | 25 июля, 2019

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

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

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

Использование потоков должно быть обоснованно, не стоит злоупотреблять и усложнять приложение там, где это не требуется. Отладка потоков требует больше времени и понимания происходящего. А неправильное применение может привести к снижению скорости и новым ошибкам которые могут появится только в результате детального тестирования. Но при правильном и обоснованном понимании это незаменимая вещь.

Многопоточное программирование начинается с класса, который называется QThread.

Для реализации многопоточного приложения можно выделить два метода. Первый метод это переопределение его виртуального метода run() который отвечает за выполнение операции в новом потоке. И второй метода это создание нового экземпляра функции QThread и помещение его в объект QObject используя moveToThread (QThread *) экземпляра QObject и вызова start() для экземпляра QThread. И установки правильного соединения сигнал слот.

Рассмотрим поподробней первый метод. Метод переопределения виртуального метода run().

Пример использования QThread

Создадим новый класс с наследованием от QThread

#include <QThread>
class Worker : public QThread 
{
   Q_OBJECT
protected: 
    void run () 
    { 
        //Код который будет выполнятся в новом потоке 
    } 
}

Далее создаем объект класса и вызываем метод start(), который вызовет переопределенный метод run()

  Worker thread;
  thread.start();

Поток запустится и завершится сразу после выполнения кода написанного в run().

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

Давайте поподробней рассмотрим данный участок кода.

Сперва подключается библиотека #include <QThread>

Далее создается класс и наследуется от QThread 

class Worker : public QThread 

Далее идет макрос Q_OBJECT который при компиляции создаст метаобъектный  код  для класса.

Метаобъектный код необходим для связывания сигналов и слотов.

Далее идет метод переопределения функции run()

protected:  
    void run(); 

Рассмотрим второй метод реализации через создание нового экземпляра QThread.

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

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

Класс Worker начинает работу при старте потока и заканчивает при сигнале finished().

// Пример реализации класса
class Worker : public QObject {
    Q_OBJECT
 
public:
    Worker();
    ~Worker();
 
public slots:
    void process();
 
signals:
    void finished();
 
private:
    
};

// Реализации методов класса.

Worker::Worker() {   
}
 

Worker::~Worker() {
 }
 

void Worker::process() {
     emit finished();
}
QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(process()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();

Стоит добавить что при использования конструктора new Worker(), не стоит создавать том другие классы, потому что они будут созданы в не в создаваемом потоке, так как перемещение в новый поток происходит через moveToThread(). При необходимости можно создать функцию initialize() и вызвать ее после moveToThread().

QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
worker-> initialize();
connect(thread, SIGNAL(started()), worker, SLOT(process()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();