Qt - 多线程

1. 线程概念的起源

1.1 单核 CPU

在早期的单核 CPU 时代还没有线程的概念,只有进程。操作系统作为一个大的“软件”,协调着各个硬件(如CPU、内存,硬盘、网卡等)有序的工作着。在双核 CPU 诞生以前,我们用的 Windows 操作系统依然可以一边用 word 写文档一边听着音乐,作为整个系统唯一可以完成计算任务的 CPU 是如何保证两个进程“同时进行”的呢?时间片轮转调度

注意这个关键字「轮转」。每个进程会被操作系统分配一个时间片,即每次被 CPU 选中来执行当前进程所用的时间。时间一到,无论进程是否运行结束,操作系统都会强制将 CPU 这个资源转到另一个进程去执行。为什么要这样做呢?因为只有一个单核 CPU,假如没有这种轮转调度机制,那它该去处理写文档的进程还是该去处理听音乐的进程?无论执行哪个进程,另一个进程肯定是不被执行,程序自然就是无运行的状态。如果 CPU 一会儿处理 word 进程一会儿处理听音乐的进程,起初看起来好像会觉得两个进程都很卡,但是 CPU 的执行速度已经快到让人们感觉不到这种切换的顿挫感,就真的好像两个进程在“并行运行”。

img

如上图所示,每一个小方格就是一个时间片,大约100ms。假设现在我同时开着 Word、QQ、网易云音乐三个软件,CPU 首先去处理 Word 进程,100ms时间一到 CPU 就会被强制切换到 QQ 进程,处理100ms后又切换到网易云音乐进程上,100ms后又去处理 Word 进程,如此往复不断地切换。我们将其中的 Word 单独拿出来看,如果时间片足够小,那么以人类的反应速度看就好比最后一个处理过程,看上去就会有“CPU 只处理 Word 进程”的幻觉。随着芯片技术的发展,CPU 的处理速度越来越快,在保证流畅运行的情况下可以同时运行的进程越来越多。

1.2 多核 CPU

随着运行的进程越来越多,人们发现进程的创建、撤销与切换存在着较大的时空开销,因此业界急需一种轻型的进程技术来减少开销。于是上世纪80年代出现了一种叫 SMP(Symmetrical Multi-Processing)的对称多处理技术,就是我们所知的线程概念。线程切换的开销要小很多,这是因为每个进程都有属于自己的一个完整虚拟地址空间,而线程隶属于某一个进程,与进程内的其他线程一起共享这片地址空间,基本上就可以利用进程所拥有的资源而无需调用新的资源,故对它的调度所付出的开销就会小很多。

img

以 QQ 聊天软件为例,上文我们一直都在说不同进程如何流畅的运行,此刻我们只关注一个进程的运行情况。如果没有线程技术的出现,当 QQ 这个进程被 CPU “临幸”时,我是该处理聊天呢还是处理界面刷新呢?如果只处理聊天,那么界面就不会刷新,看起来就是界面卡死了。有了线程技术后,每次 CPU 执行100ms,其中30ms用于处理聊天,40ms用于处理传文件,剩余的30ms用于处理界面刷新,这样就可以使得各个组件可以“并行”的运行了。于是乎我们可以提炼出两点关于多线程的适用场景:

  • 通过使用多核 CPU 提高处理速度。

  • 保证 GUI 界面流畅运行的同时可以执行其他计算任务。

2. QThread类

Qt的线程类为QThread,是独立于平台的线程操作类。

2.1 公共函数

//返回指向线程的事件调度程序对象的指针。如果该线程不存在事件分派器,则此函数返回nullptr.l
QAbstractEventDispatcher *eventDispatcher() const
//告诉线程的事件循环退出并返回代码。
void exit(int returnCode = 0)
//如果线程完成返回true;否则返回false。
bool isFinished() const
//如果在此线程上运行的任务应该停止,则返回true。可以通过requestinterrupt()请求中断。
bool isInterruptionRequested() const
//如果线程正在运行则返回true;否则返回false。
bool isRunning() const
//返回线程的当前事件循环级别。
int loopLevel() const
//返回正在运行的线程的优先级。如果线程没有运行,这个函数返回InheritPriority。
QThread::Priority priority() const
//请求线程的中断。该请求是建议性的,由线程上运行的代码决定是否以及如何对该请求采取行动。此函数不会停止线程上运行的任何事件循环,也不会以任何方式终止它。
void requestInterruption()
//将线程的事件分派器设置为eventDispatcher。这只有在还没有为线程安装事件分派器的情况下才有可能。也就是说,在线程被start()启动之前,或者在主线程的情况下,在QCoreApplication被实例化之前。这个方法获得了对象的所有权。
void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)
//这个函数为正在运行的线程设置优先级。如果线程没有运行,这个函数什么都不做并立即返回。使用start()启动具有特定优先级的线程。
void setPriority(QThread::Priority priority)
//将线程的最大堆栈大小设置为stackSize。如果stackSize大于0,则最大堆栈大小设置为stackSize字节,否则由操作系统自动确定最大堆栈大小。
void setStackSize(uint stackSize)
//返回线程的最大堆栈大小(如果使用setStackSize()设置);否则返回零。
uint stackSize() const
//阻塞线程,直到满足这些条件中的任何一个
bool wait(unsigned long time = ULONG_MAX)

 

2.2 静态公共函数

//创建线程,创建一个新的QThread对象,它将执行函数f。
[static] QThread *create(Function &&f)
//获取当前线程对象地址
[static] QThread *QThread::currentThread()
//获取可在系统上运行的理想线程数。 这是通过查询系统中实际的和逻辑的处理器核数来完成的。 如果无法检测到处理器核数,则该函数返回1。 
[static] int QThread::idealThreadCount()
//强制当前线程休眠n毫秒。      
[static] void QThread::msleep(unsigned long msecs)   
//强制当前线程休眠n秒  
[static] void QThread::sleep(unsigned long secs)    
//强制当前线程休眠n微秒      
[static] void QThread::usleep(unsigned long usecs)  
//放弃执行当前线程, 把机会让给别的线程,注意,操作系统决定切换到哪个线程。  
[static] void QThread::yieldCurrentThread()

 

QThread常用函数及注意事项

以下是一些常用的QThread函数:

  • start(): 启动线程,使线程进入运行状态,调用线程的run()方法。
  • run(): 线程的执行函数,需要在该函数中编写线程所需执行的任务。
  • quit(): 终止线程的事件循环,在下一个事件处理周期结束时退出线程。
  • wait(): 阻塞当前线程,直到线程执行完成或超时。
  • finished(): 在线程执行完成时发出信号。
  • terminate(): 强制终止线程的执行,不推荐使用,可能导致资源泄漏和未定义行为。
  • isRunning(): 判断线程是否正在运行。
  • currentThreadId(): 返回当前线程的ID。
  • yieldCurrentThread(): 释放当前线程的时间片,允许其他线程执行。
  • setPriority(): 设置线程优先级。
  • msleep(): 让当前线程休眠指定的毫秒数。

在使用QThread类中的常用函数时,有一些注意事项需要注意:

  • start()函数: 调用start()函数启动线程时,会自动调用线程对象的run()方法。不要直接调用run()方法来启动线程,应该使用start()函数。
  • wait()函数: wait()函数会阻塞当前线程,直到线程执行完成。在调用wait()函数时需要确保不会发生死锁的情况,避免主线程和子线程相互等待对方执行完成而无法继续。
  • terminate()函数: 调用terminate()函数会强制终止线程,这样可能会导致资源未能正确释放,造成内存泄漏等问题。因此应该尽量避免使用terminate()函数,而是通过设置标志让线程自行退出。
  • quit()函数: quit()函数用于终止线程的事件循环,通常与exec()函数一起使用。在需要结束线程事件循环时,可以调用quit()函数。
  • finished信号: 当线程执行完成时会发出finished信号,可以连接这个信号来处理线程执行完成后的操作。
  • yieldCurrentThread()函数: yieldCurrentThread()函数用于让当前线程让出时间片,让其他线程有机会执行。使用时应该注意避免过多的调用,否则会影响程序性能。

 

2.3 信号与槽

信号:

[signal] void QThread::started()//这个信号在相关线程开始执行时,在run()函数被调用之前发出。

[signal] void QThread::finished()//这个信号在相关线程完成执行之前从它发出

槽函数:

[slot] void QThread::quit()//告诉线程的事件循环以返回码0 (success)退出。

[slot] void QThread::start(Priority priority = InheritPriority)//通过调用run()开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,这个函数什么也不做。

[slot] void QThread::terminate()//终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调度策略。

 

 

3. 使用多线程

Qt 使用多线程有三种方式:使用静态函数、继承自QThread、使用 moveToThread()

3.1 第一种:静态函数

注:在 Qt 5.10 及更高版本中可以这样来创建一个线程,使用QThread::create()来直接创建一个线程

QThread有两个静态成员函数create,创建一个新的QThread对象,它将使用参数args执行函数f。(必须在C++17及以上才可以使用)

[static] QThread *QThread::create(Function &&f, Args &&... args)    //C++17
[static] QThread *QThread::create(Function &&f)

新线程没有启动——它必须通过显式调用start()来启动。 这允许您连接到它的信号,将QObjects移动到线程,选择新线程的优先级,等等。 函数f将在新线程中被调用。

注意:不要对返回的QThread实例多次调用start(); 这样做将导致未定义的行为。

全局、类的静态、非静态函数做入口函数

void print()
{
    for(int i = 0;i<5;i++)
        qInfo()<<"hello global print";
}
class MainWindow:public QWidget
{
    Q_OBJECT
public:
    MainWindow(QWidget*parent = nullptr):QWidget(parent)
    {
        qInfo()<<"end";
    }
    static void print1()
    {
        for(int i = 0;i<5;i++)
            qInfo()<<"hello static print1";
    }
    void print2()
    {
        for(int i = 0;i<5;i++)
            qInfo()<<"hello print2";
    }
};
  • QThread::create使用全局函数作为入口函数。

auto* thr = QThread::create(print);
thr->start();
  • QThread::create使用类的静态函数作为入口函数。

auto* thr1 = QThread::create(&MainWindow::print1);
thr1->start();
  • QThread::create使用类的成员函数作为入口函数。

auto* thr2 = QThread::create(&MainWindow::print2,&w);
thr2->start();
  • QThread::create使用Lambda表达式作为入口函数。

auto* thr3 = QThread::create([](){qInfo()<<"hello lambda";});
thr3->start();

 

给入口函数传参

void print(const QString& str)
{
    for(int i = 0;i<5;i++)
    {
        qInfo()<<"hello global print"<<str;
    }
}
​
auto* thr = QThread::create(print,"I Like Qt");
thr->start();

 

3.2 第二种:继承自QThread

这种方法,实际上是继承 QThread 的类,然后通过通过重写 run 函数来实现多线程。

 

示例代码一:

MyThread.h

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    ~MyThread();
    
protected:
    void run() override;
    
signals:
};

MyThread.cpp

#include "mythread.h"
#include <QDebug>

MyThread::MyThread(QObject *parent): QThread(parent)
{
    
}

MyThread::~MyThread()
{
    qDebug()<<   "MyThread::~MyThread()";
}

void MyThread::run()
{
    int i= 0;
    while(i<200)
    {
        QThread::msleep(20);
        if(i==150)
            break;
        qDebug()<<"i"<<i++;
    }
}

Widget.h

#include <QWidget>
#include "mythread.h"

namespace Ui {
class Widget;
}

class Widget: public QWidget
{
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    
private:
    Ui::Widget *ui;
    MyThread* mythread;
};

Widget.cpp

#include "widget.h"
#include<QDebug>
#include<QThread>

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    mythread = new MyThread(this);//创建线程
    connect(mythread,&QThread::started,this,[](){qDebug()<<"start";});
    connect(mythread,&QThread::finished,this,[](){qDebug()<<"finished";});
    mythread->start();//启动线程
}

Widget::~Widget()
{
    mythread->quit();//退出线程
    delete mythread;
    delete ui;
}

 

示例代码二:

Worker.h

#ifndef WORKER_H
#define WORKER_H
 
#include <QThread>
 
class Worker : public QThread
{
public:
    Worker();
 
    void run();
 
    void printFunc();
 
 
};
 
#endif // WORKER_H

Worker.cpp

#include "Worker.h"
#include <QDebug>
 
Worker::Worker()
{
 
}
 
void Worker::run()
{
    qDebug()<<"子线程ThreadID: "<<QThread::currentThreadId();
}
 
void Worker::printFunc()
{
    qDebug()<<"子线程成员函数ThreadID: "<<QThread::currentThreadId();
}

main函数:

#include <iostream>
#include <QDebug>
#include "Worker.h"
 
using namespace std;
 
int main()
{
    Worker w;
 
    w.start();
 
    qDebug()<<"主线程ThreadID: "<<QThread::currentThreadId();
 
    w.printFunc();
 
    while (1)
    {
 
    }
 
    return 0;
}

 

3.3 第三种:moveToThread

moveToThread() 是 Qt 中用于将对象移动到另一个线程的方法。通过调用 moveToThread() 函数,可以将一个 QObject 对象从当前线程移动到另一个线程中,从而实现对象在新线程中执行特定的任务。

在多线程编程中,通常会使用 moveToThread() 方法来将耗时的任务或需要在单独线程中执行的逻辑移动到单独的线程中,以避免阻塞主线程(通常是 GUI 线程)的执行。

 

示例代码一:

移动线程步骤:

  1. 创建一个新的类(工作类对象MyWork),必须继承自QObject
  2. 在类中添加一个成员函数,函数名称、签名等可自定义
  3. 在主线程中创建一个QThread对象QThread* sub = new QThread;
  4. 在主线程中创建工作类对象,千万不能指定父对象 MyWork* work = new MyWork
  5. 将工作类对象移动到子线程中work->moveToThread(sub)
  6. 启动子线程sub.start(),但是线程并没有工作
  7. 调用工作类对象中的工作函数,子线程开始工作

mywork.h

//mywork.h

#ifndef MYWORK_H
#define MYWORK_H

#include <QObject>

class MyWork : public QObject
{
    Q_OBJECT
public:
    explicit MyWork(QObject *parent = nullptr);

    // 工作函数
    void working();

signals:
    void curNumber(int num);

public slots:
};

#endif // MYWORK_H

mywork.cpp

//mywork.cpp

#include "mywork.h"
#include <QDebug>
#include <QThread>

MyWork::MyWork(QObject *parent) : QObject(parent)
{

}

void MyWork::working()
{
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while(1)
    {
        emit curNumber(num++);
        if(num == 10000000)
        {
            break;
        }
        QThread::usleep(1);
        qDebug()<<"num = "<<num;
    }
    qDebug() << "run() 执行完毕, 子线程退出...";
}

mainwindow.cpp

//mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include "mywork.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug() << "主线程对象的地址: " << QThread::currentThread();

    // 创建线程对象
    QThread* sub = new QThread;
    // 创建工作的类对象
    // 千万不要指定给创建的对象指定父对象
    // 如果指定了: QObject::moveToThread: Cannot move objects with a parent
    MyWork* work = new MyWork;
    // 将工作的类对象移动到创建的子线程对象中
    work->moveToThread(sub);
    // 启动线程
    sub->start();
    // 让工作的对象开始工作, 点击开始按钮, 开始工作
    connect(ui->startBtn, &QPushButton::clicked, work, &MyWork::working);
    // 显示数据
    connect(work, &MyWork::curNumber, this, [=](int num)
    {
        ui->label->setNum(num);
    });
}

MainWindow::~MainWindow()
{
    delete ui;
}

执行结果:

线程资源释放

  1. 直接利用对象树,在创建对象的时候制定父对象
  2. 直接析构,如果是在函数范围内声明的QThread对象,析构函数不能够访问可以这样
connect(this,&MainWindow::destroy,this,[=](){//主窗体析构的时候会触发destroy信号
    t1->quit();
    t1->wait();
    t1->deleteLater();
});

 

示例代码二:

通常处理ui卡死最常用的办法,就是将耗时操作放进线程中执行,等待线程执行完毕后发信号出来通知,当前耗时操作已经被执行完,允许进入下一环节。
Qt线程创建的方式有两种,一种是继承至QThread,之后重写run()方法来实现线程的创建,另一种是继承至QObject,通过moveToThread的方式创建,官方推荐使用后者,两者的区别是:
moveToThread 方法,是把我们需要的工作全部封装在一个类中,将每个任务定义为一个槽函数,再建立触发这些槽函数的信号,然后连接信号和槽,最后调用 moveToThread 方法将这个类交给一个 QThread 对象,再调用 QThread 的 start() 函数使其全权处理事件循环。于是,任何时候我们需要让子线程执行某个任务,只需要发出对应的信号就可以。
其优点是我们可以在一个worker类中定义很多个需要做的工作,然后触发信号,子线程就可以执行。相比于继承 QThread 方法,只能执行 run() 函数中的任务,moveToThread 的方法中一个线程可以做很多不同的工作,只要实现对应的槽函数,触发对应的信号即可,针对第二种方法,有如下代码:
 
Worker.h
#ifndef DEL_WORKER_H
#define DEL_WORKER_H

#include <QObject>
#include <QDebug>
#include <QThread>

class Worker : public QObject
{
Q_OBJECT

public:
    explicit Worker(QObject *parent = nullptr);

public slots:
    void doWork(int parameter);  // doWork 定义了线程要执行的操作


signals:
    void resultReady(const int result);  // 线程完成工作时发送的信号
};

#endif //DEL_WORKER_H

Worker.cpp

#include "Worker.h"

#include <QDebug>
#include <QThread>


Worker::Worker(QObject *parent): QObject(parent)
{

}

void Worker::doWork(int parameter)
{
    qDebug() << "receive the execute signal" ;
    qDebug() << "\tCurrent thread ID: " << QThread::currentThreadId();

    // 循环一百万次
    for (int i = 0; i != 1000000; ++i)
    {
        ++parameter ;
        qDebug() <<"parameter="<<parameter;
    }

    // 发送结束信号
    qDebug() << "\tFinish the work and sent the result Ready signal\n" ;
    emit resultReady(parameter);
}
Controller.h
Controller 用于 启动子线程 和 处理子线程执行的结果(Controller用于管理线程)
#ifndef DEL_CONTROLLER_H
#define DEL_CONTROLLER_H

#include <QObject>
#include <QThread>

// controller 用于 启动子线程 和 处理子线程执行的结果(controller用于管理线程)
class Controller : public QObject
{
    Q_OBJECT

public:
    explicit Controller(QObject *parent = nullptr);
    ~Controller();

private:
    QThread thread ;

public slots:
    static void handleResults(int result);  // 处理子线程执行的结果

signals:
    void operate(const int);  // 发送信号,触发线程

};

#endif //DEL_CONTROLLER_H

Controller.cpp

#include "Controller.h"
#include "worker.h"

Controller::Controller(QObject *parent): QObject(parent)
{
    Worker *worker = new Worker ;

    // 调用 moveToThread 将该任务交给 workThread
    worker->moveToThread(&thread);

    // operate 信号发射后启动线程工作
    connect(this, SIGNAL(operate(const int)), worker, SLOT(doWork(int)));

    // 该线程结束时销毁
    connect(&thread, &QThread::finished, worker, &QObject::deleteLater);

    // 线程结束后发送信号,对结果进行处理
    connect(worker, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));

    // 启动线程
    thread.start();

    // 发射信号,开始执行
    qDebug() << "emit the signal to execute!" ;
    qDebug() << "\tCurrent thread ID:" << QThread::currentThreadId() << '\n' ;

    emit operate(0);
}

Controller::~Controller()
{
    thread.quit();
    thread.wait();
};

void Controller::handleResults(int result)  // 处理子线程执行的结果
{
    qDebug() << "receive the resultReady signal" ;
    qDebug() << "\tCurrent thread ID: " << QThread::currentThreadId() << '\n' ;
    qDebug() << "\tThe last result is: " << result ;
}

 main.cpp

#include <QApplication>

#include "Controller.h"
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Controller ctr;


    return a.exec();
}

运行结果:

小知识

1.在主线程退出之前,析构函数中总是需要先停掉子线程,这是比较合理的操作,否则系统报错误:主线程先于子线程退出
2.QThread中的静态方法currentThreadId()可以返回当前线程句柄
3.QtConCurrent的线程是线程安全型的

 

示例代码三:

  moveToThread() 是 Qt 中用于将对象移动到另一个线程的方法。通过调用 moveToThread() 函数,可以将一个 QObject 对象从当前线程移动到另一个线程中,从而实现对象在新线程中执行特定的任务。

线程类:

Worker.h

#ifndef WORKER_H
#define WORKER_H

#include <QObject>

class Worker : public QObject
{
    Q_OBJECT
public:
    Worker();

    void printFunc();

public slots:
    void doWork();

    void doWork2();

signals:
    void started_doWork2();

};

#endif // WORKER_H

Worker.cpp

#include "Worker.h"
#include <QDebug>
#include <QThread>

Worker::Worker()
{

}

void Worker::printFunc()
{
    qDebug() << "成员函数ThreadID:"<<QThread::currentThreadId();

}

void Worker::doWork()
{
    qDebug() << "子线程 doWork ThreadID:"<<QThread::currentThreadId();
    static int i = 0;
    while (1)
    {
        i++;
        qDebug()<<"doWork i = "<<i;
        QThread::sleep(2);
    }
}

void Worker::doWork2()
{
    qDebug() << "子线程 doWork2 ThreadID:"<<QThread::currentThreadId();
    static int i = 0;
    while (1)
    {
        i++;
        qDebug()<<"doWork2 i = "<<i;
        QThread::sleep(2);
    }
}

main.cpp

//#include "mainwindow.h"

#include <QApplication>
#include "Worker.h"
#include <QDebug>
#include <QThread>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //MainWindow w;
    //w.show();

    Worker worker;
    QThread thread;

    worker.moveToThread(&thread);

    QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork);         //第一种连接方式
    //QObject::connect(&worker, &Worker::started_doWork2, &worker, &Worker::doWork2);     //第二种连接方式

    //启动线程
    thread.start();

    //调用成员数
    worker.printFunc();

    //发送自定义信号,执行线程处理函数
    //emit worker.started_doWork2();

    while (1)
    {
        qDebug() << "主线程 ThreadID:"<<QThread::currentThreadId();
        QThread::sleep(2);
    }

    return a.exec();
}

运行结果:

结果分析:

  • 槽函数无论是线程的信号触发还是自定义信号触发,槽函数都在新线程里运行。
  • 成员函数和主函数运行在主线程当中。

 

3.4 后两种方式的优缺点

QThread 方式:

使用场景:

  • 当需要创建一个独立的线程来执行某个任务,且需要对线程的整个生命周期进行管理时,适合使用 QThread 方式。
  • 当任务逻辑相对简单或独立,不需要频繁地进行线程间通信时,可以选择使用 QThread 方式。

优点:

  • 可以直接控制线程的生命周期,包括启动、停止、等待线程退出等。
  • 适合单一任务的线程处理,结构相对清晰易懂。
  • 相对直观,可以比较容易理解和使用。

缺点:

  • 需要手动管理线程之间的通信和数据共享,容易引入线程安全问题。
  • 繁琐的线程管理和同步机制可能增加代码复杂度和风险。

moveToThread() 方式:

使用场景:

  • 当需要将一个 QObject 对象移动到指定的线程中执行任务,或者需要多个对象在同一线程中协同工作时,适合使用 moveToThread() 方式。
  • 当需要灵活地控制对象和线程之间的关系,进行复杂的线程间通信时,可以选择使用 moveToThread() 方式。

优点:

  • 可以利用信号和槽机制方便地实现对象在不同线程中的通信。
  • 可以更灵活地管理对象和线程的关系,避免直接操作线程带来的问题。
  • 适合处理复杂的多线程通信和任务分发。

缺点:

  • 无法直接控制线程的启动和停止,线程的生命周期由对象决定,可能使得线程管理稍显复杂。
  • 对对象的线程移动可能引入一些额外的开销,需要谨慎设计线程之间的交互逻辑。

 

总结:
        选择使用 QThread 或 moveToThread() 方式创建线程取决于具体需求和情况。可以根据以下原则进行选择:

  1. 如果需要独立管理整个线程的生命周期、简单的多线程操作,并且不涉及复杂的线程间通信,可以选择 QThread 方式。
  2. 如果需要灵活地管理对象与线程之间的关系、复杂的多线程通信和任务分发,可以选择 moveToThread() 方式。
  3. 综上所述,根据项目需求、任务复杂度和开发方便性来选择适合的创建线程方式。

 

4. 安全退出线程

4.1 线程退出方式

  • QThread::terminate() - 不安全
    官方说明
    终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调度策略。请在terminate()之后使用QThread::wait()。
    当线程终止时,所有等待该线程完成的线程将被唤醒。
    警告:此函数是危险的,不鼓励使用。线程可以在其代码路径中的任何点终止。线程可以在修改数据时终止。线程没有机会自己清理,解锁任何持有的互斥锁等。简而言之,只有在绝对必要时才使用这个函数。
    终止可以通过调用QThread::setTerminationEnabled()显式地启用或禁用。在终止被禁用时调用此函数将导致延迟终止,直到重新启用终止。有关更多信息,请参阅QThread::setTerminationEnabled()的文档。
    我非常不建议大家使用这个函数,一旦使用这个函数,将会对我们的程序造成隐患,这在大型工程中是致命性的
  • QThread::exit(int returnCode = 0) - 正确使用才安全
    官方说明
    用返回代码告诉线程的事件循环退出。
    调用此函数后,线程离开事件循环并从QEventLoop::exec()调用中返回。函数的作用是:返回returnCode。
    按照惯例,returnCode为0表示成功,任何非零值表示错误。
    注意,与同名的C库函数不同,这个函数确实返回给调用者——停止的是事件处理。
    在QThread::exec()被再次调用之前,QEventLoops将不会在这个线程中启动。如果QThread::exec()中的eventloop没有运行,那么下一次调用QThread::exec()也会立即返回
  • QThread::quit 正确使用才安全
    官方说明
    告诉线程的事件循环退出,返回代码为0(成功)。相当于调用QThread::exit(0)。
    如果线程没有事件循环,则此函数不执行任何操作。

 

4.2 线程安全退出

重写run函数的方式退出,这个我们好控制。但是使用moveToThread的方式,在退出线程的时候,多数同学经常会碰到异常崩溃的情况,基本上都是报错 Destroyed while thread is still running ,说明线程还没有退出循环就被强制释放了资源,意思就是线程还在运行过程中被释放(delete)就造成了崩溃。所以我们在给线程发送退出指令之后,还要等待线程执行完此次事件循环,再才能delete掉这个thread指针。

例如使用了以下的代码( thread初始化方是 new QThread)释放资源

thread->quit();
delete thread;

或者使用了以下代码

thread->terminate();
delete thread;

这两段代码都是非常不安全的代码,不要使用。

 

安全的退出方式

使用QThread的finished信号绑定QObject的deleteLater函数实现资源自动释放,也可以绑定一个函数。退出时只需要调用quit()函数即可,如果绑定的是函数,则可以在适当的位置释放thread对象和Object对象

deleteLater函数可以查看Qt文档,这里就不做说明了

线程创建以及退出示例

  • 创建
	TestObject* object = new TestObject;
	QThread* thread = new QThread;
	object->moveToThread(thread );
	connect(thread,&QThread::finished,object,&TestObject::deleteLater);	// 退出后释放TestObject对象资源
	connect(thread,&QThread::finished,thread,&QThread::deleteLater);	// 退出后释放QThread对象资源
	thread->start();
  • 退出
	thread->quit();	// 也可以使用thread->exit(0);
	thread->wait(); // wait函数是个阻塞的接口,意思是线程必须真的退出了,才会执行wait之后的语句,否则将会一直阻塞在这里,如果在界面线程上使用,需要保证线程中代码的合理性。
	thread = nullptr;
	object = nullptr;

 

5. 多任务并发执行

如果有多个复杂处理任务,可以创建多个线程对象分别移动到线程中执行。除了此方法还可以使用Qt线程池(QThreadPool)来现实多任务并发执行

5.1 moveToThread实现

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "worker.h"
#include <QThread>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();

    void setLable(int number);
    void setLable2(int number);
    void setLable3(int number);

private:
    Ui::Widget *ui;

    Worker worker;
    QThread thread;

    Worker worker2;
    QThread thread2;

    Worker worker3;
    QThread thread3;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);

    worker.moveToThread(&thread);
    connect(&worker, &Worker::startedWork, &worker, &Worker::doWork);         //第一槽函数
    //启动线程
    thread.start();

    worker2.moveToThread(&thread2);
    connect(&worker2, &Worker::startedWork2, &worker2, &Worker::doWork2);         //第一槽函数
    //启动线程
    thread2.start();

    worker3.moveToThread(&thread3);
    connect(&worker3, &Worker::startedWork3, &worker3, &Worker::doWork3);         //第一槽函数
    //启动线程
    thread3.start();

    connect(&worker, &Worker::updataWork, this , &Widget::setLable);
    connect(&worker2, &Worker::updataWork2, this , &Widget::setLable2);
    connect(&worker3, &Worker::updataWork3, this , &Widget::setLable3);
}

Widget::~Widget()
{
    qDebug()<<"~Widget";
    thread.quit();
    thread.wait();
    thread2.quit();
    thread2.wait();
    thread3.quit();
    thread3.wait();

    delete ui;
}


void Widget::on_pushButton_clicked()
{
    worker.stateWork();
    worker2.stateWork2();
    worker3.stateWork3();
}

void Widget::setLable(int number)
{
    ui->label->setText(QString::number(number));
}

void Widget::setLable2(int number)
{
    ui->label_2->setText(QString::number(number));
}

void Widget::setLable3(int number)
{
    ui->label_3->setText(QString::number(number));
}

worker.h

#ifndef WORKER_H
#define WORKER_H

#include <QObject>

class Worker : public QObject
{
    Q_OBJECT
public:
    Worker();

    //包装了一下执行线程的信号
    void stateWork();
    void stateWork2();
    void stateWork3();

public slots:
    //线程处理函数
    void doWork();
    void doWork2();
    void doWork3();

signals:
    //用于执行线程函数
    void startedWork();
    void startedWork2();
    void startedWork3();

    //用于把线程函数中的数据传递到主界面中并更新
    void updataWork(int number);
    void updataWork2(int number);
    void updataWork3(int number);
};

#endif // WORKER_H

worker.cpp

#include "worker.h"
#include <QDebug>
#include <QThread>

Worker::Worker()
{

}

void Worker::stateWork()
{
    emit startedWork();
}

void Worker::stateWork2()
{
    emit startedWork2();
}

void Worker::stateWork3()
{
    emit startedWork3();
}

void Worker::doWork()
{
    qDebug() << "doWork ThreadID:"<<QThread::currentThreadId();
    static int i = 0;
    while (1)
    {
        i++;
        emit updataWork(i);//发送信号更新界面数据
        qDebug()<<"doWork i = "<<i;
        QThread::sleep(2);
    }
}

void Worker::doWork2()
{
    qDebug() << "doWork2 ThreadID:"<<QThread::currentThreadId();
    static int i = 100;
    while (1)
    {
        i++;
        emit updataWork2(i);
        qDebug()<<"doWork2 i = "<<i;
        QThread::sleep(2);
    }
}

void Worker::doWork3()
{
    qDebug() << "doWork3 ThreadID:"<<QThread::currentThreadId();
    static int i = 200;
    while (1)
    {
        i++;
        emit updataWork3(i);
        qDebug()<<"doWork3 i = "<<i;
        QThread::sleep(2);
    }
}

main.cpp

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

运行效果:

 

5.2 QThread实现

mythread.h

#include <QThread>

//=============================================================//
class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    ~MyThread();

protected:
    void run() override;

signals:
    //用于把线程函数中的数据传递到主界面中并更新
    void updata(int number);

signals:
};

//=============================================================//
class MyThread2 : public QThread
{
    Q_OBJECT
public:
    explicit MyThread2(QObject *parent = nullptr);
    ~MyThread2();

protected:
    void run() override;

signals:
    //用于把线程函数中的数据传递到主界面中并更新
    void updata2(int number);

signals:
};

//=============================================================//
class MyThread3 : public QThread
{
    Q_OBJECT
public:
    explicit MyThread3(QObject *parent = nullptr);
    ~MyThread3();

protected:
    void run() override;

signals:
    //用于把线程函数中的数据传递到主界面中并更新
    void updata3(int number);

signals:
};

mythread.cpp

#include "mythread.h"
#include <QDebug>

//=============================================================//
MyThread::MyThread(QObject *parent): QThread(parent)
{

}

MyThread::~MyThread()
{
    qDebug()<<   "MyThread::~MyThread()";
}

void MyThread::run()
{
    qDebug() << "doWork ThreadID:"<<QThread::currentThreadId();
    static int i = 0;
    while (1)
    {
        i++;
        emit updata(i);//发送信号更新界面数据
        qDebug()<<"doWork i = "<<i;
        QThread::sleep(2);
    }
}

//=============================================================//
MyThread2::MyThread2(QObject *parent): QThread(parent)
{

}

MyThread2::~MyThread2()
{
    qDebug()<<   "MyThread::~MyThread()";
}

void MyThread2::run()
{
    qDebug() << "doWork ThreadID:"<<QThread::currentThreadId();
    static int i = 100;
    while (1)
    {
        i++;
        emit updata2(i);//发送信号更新界面数据
        qDebug()<<"doWork i = "<<i;
        QThread::sleep(2);
    }
}

//=============================================================//
MyThread3::MyThread3(QObject *parent): QThread(parent)
{

}

MyThread3::~MyThread3()
{
    qDebug()<<   "MyThread::~MyThread()";
}

void MyThread3::run()
{
    qDebug() << "doWork ThreadID:"<<QThread::currentThreadId();
    static int i = 500;
    while (1)
    {
        i++;
        emit updata3(i);//发送信号更新界面数据
        qDebug()<<"doWork i = "<<i;
        QThread::sleep(2);
    }
}

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "mythread.h"

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void setLable(int number);
    void setLable2(int number);
    void setLable3(int number);

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;

    MyThread* mythread;
    MyThread2* mythread2;
    MyThread3* mythread3;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);

    mythread = new MyThread(this);//创建线程
    connect(mythread, &MyThread::updata, this , &Widget::setLable);

    mythread2 = new MyThread2(this);//创建线程
    connect(mythread2, &MyThread2::updata2, this , &Widget::setLable2);

    mythread3 = new MyThread3(this);//创建线程
    connect(mythread3, &MyThread3::updata3, this , &Widget::setLable3);
}

Widget::~Widget()
{
    mythread->quit();//退出线程
    delete mythread;

    delete ui;
}

void Widget::setLable(int number)
{
    //ui->label->setText(QString::number(number));
    ui->lcdNumber->display(number);
}

void Widget::setLable2(int number)
{
    //ui->label->setText(QString::number(number));
    ui->lcdNumber_2->display(number);
}

void Widget::setLable3(int number)
{
    //ui->label->setText(QString::number(number));
    ui->lcdNumber_3->display(number);
}


void Widget::on_pushButton_clicked()
{
    mythread->start();//启动线程
    mythread2->start();//启动线程
    mythread3->start();//启动线程
}

main.cpp

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

运行效果:

posted @ 2024-11-13 14:08  [BORUTO]  阅读(4)  评论(0编辑  收藏  举报