QT中多线程写法

发布于:2025-04-16 ⋅ 阅读:(21) ⋅ 点赞:(0)

转自个人博客:QT中多线程写法

1. QThread及moveToThread()

使用情况:

多使用于需要将有着复杂逻辑或需要一直占用并运行的放入子线程中执行的情况,moveToThread是将整个类的对象移入子线程。

优缺点:

  • 优点:更符合QT的逻辑设计;灵活使用管理线程;将类的任务逻辑与线程逻辑区分开。
  • 缺点:不易于单独对指定函数单独放入子线程,也就不易于将一个类中多个函数放入多个子线程执行。

使用方法:

创建目标类对象后为其创建一个对应的QThread对象,利用信号和槽的机制,将类的执行与线程的触发连接起来,就可以利用开启线程来开始任务的执行。

注意,moveToThread的对象可以是QObject等,但不能是GUI相关的,如QWidget等。

#include <QObject>

class worker : public QObject {
    Q_OBJECT
public:
    worker(...) {...}
    
public slots:
    void doWork();
};
#include <QObject>
#include <QThread>

class mainClass : public QObject {
    Q_OBJECT
public slots:
    mainClass(){
        m_worker = new worker();
        m_thread = new QThread();
        connect(m_thread, &QThread::started, m_worker, &worker::doWork);
    	m_worker->moveToThread(m_thread); // 将类对象移动到子线程中
        m_thread->start(); //通过线程的开启来执行任务
    }
    
private:
    worker *m_worker;
    QThread *m_thread;
    
};

2. 继承QThread类

2.1 QThread基础使用

使用情况:

  1. 用于自定义QThread类,扩充原始QThread的功能,使其更加满足自己的需求,本质上还是用moveToThread(),只是声明的QThread对象换成了自定义类的对象
  2. 用于直接将需要放入子线程执行的类继承QThread,通过重载run()方法来控制任务的执行等,run()后就直接在子线程中执行任务了。

优缺点:

  • 优点:因为是直接继承整个QThread类,就可以重写其中的函数来实现指定的效果,对线程的控制更为底层,进入子线程的方法更为简单。
  • 缺点:这样也就需要自己把握好对线程生命周期的管理,同时也会将线程逻辑与任务逻辑结合在一起了;且不易于利用信号和槽等机制与其他线程进行通信。

使用方法:

继承QThread,一定需要重载run()来控制任务的运行,只有通过run()运行的任务才是运行在子线程中的

#include <QThread>
#include <QObject>

class MyThread : public QThread {
    Q_OBJECT
public:
    MyThread(QObject* parent = nullptr) : QThread(parent), m_stop(false) {}

    void run() override { // 重载run的方法
        while(m_isRun) {
            doWoker();
        }
    }
    
    void stop() {
        m_isRun = false;
    }
    
private:
    void doWoker() {
        ....
    }
    
private:
    bool m_isRun = true; // 自定义控制任务的关闭,可以采用其他方法
};
#include <QObject>

class mainClass : public QObject {
    Q_OBJECT
public slots:
    mainClass(){
		m_thread = new MyThread();
		m_thread->start(); // 直接通过start()进行run
    }
    
private:
    MyThread *m_thread;
    
};

2.2 QThread类相关方法

  1. run()

    virtual void run();

    程序的入口,自定义多线程时,继承后必须要重载的。通常将任务逻辑写在其中,使用run()运行任务逻辑才能在子线程中运行任务。

  2. *start()

    void start(QThread::Priority priority = QThread::InheritPriority);

    用于启动线程,线程会从run()方法开始执行任务,可以通过设置参数指定线程的优先级,优先级依次递增为:

    • QThread::IdlePriority:最小的优先级
    • QThread::LowestPriority
    • QThread::LowPriority
    • QThread::NormalPriority
    • QThread::HighPriority
    • QThread::HighestPriority
    • QThread::TimeCriticalPriority
    • QThread::InheritPriority:继承父线程的优先级
  3. exec()

    int exec();

    启动线程的事件循环。对于需要在子线程中处理信号和槽的情况,通常需要调用 exec() 来让线程进入事件循环。

  4. exit()

    void exit(int returnCode = 0);

    用于退出线程的事件循环,并将给定的返回码传递给 exec()。这不会立即终止线程,只是结束事件循环。

  5. quit()

    void quit();

    用于安全退出线程的事件循环,与 exit() 类似。它会在事件循环下一次检查事件时退出,并安全地结束线程。

  6. wait()

    bool wait(unsigned long time = ULONG_MAX);

    用于阻塞调用的线程,直到目标线程结束。可以通过设置时间参数来设定最多阻塞的毫秒数。通常用于等待线程的自然结束,确保所有任务执行完毕。

  7. terminate()

    void terminate();

    强制终止线程的执行。这是一个不安全的方法,不会释放线程正在使用的资源,因此尽量避免使用。推荐使用 quit()wait() 的方式来结束线程。

3. QtConcurrent和QFuture

3.1 QtConcurrent基础使用

使用情况:

多使用于需要将单一可调用对象(如函数等)放入子线程执行,可以更便捷地完成简单的并行任务;当对于一个类中,需要对多个方法放入子线程执行,使用这个方法也可以比其他方法更便捷地实现。

优缺点:

  • 优点:使用方法更简便;因为是直接对函数这种对象操作的,对应用场景的要求也就不高,更适用于轻量级并发任务;并且其可以自行管理线程,不需要手动释放等操作;也提供了较多方法可自定义。
  • 缺点:缺少更精细的底层多线程逻辑控制;不适合复杂的任务情景,不易用信号和槽机制。

使用方法:

使用QtConcurrent::run()方法直接将指定函数等对象移动到子线程并执行,调用参数和不调用参数的语法可参考下列示例,当然也支持使用Lambda表达式简洁化函数。

QtConcurrent返回QFuture对象以管理和监控异步任务执行结果,通过QFuture可以获取异步任务执行的结果或状态,绑定一个QFuture对象后可以在主线程任意获取此线程的相关状态。

#include <QObject>

class mainClass : public QObject {
    Q_OBJECT
public slots:
    mainClass(){
        // 1.无需调参
		QtConcurrent::run(this, &mainClass::doWork1); 
        
        // 2.调用参数,并绑定QFuture对象,获取执行结果
        int workValue = 10;
        QFuture<int> futureWork2 = QtConcurrent::run(this, &mainClass::doWork2, workValue); 
        futureWork2.waitForFinished();
        qDebug() << futureWork2.result();
        
        // 3.使用正则Lambda表达式代替目标参数
    }
    
    void doWork1(){...};
    int doWork2(int value){...};
    
private:
    MyThread *m_thread;
    
};

3.2 QtConcurrent相关方法

  1. QtConcurrent::run()

    参考3.1,执行单一异步任务。

  2. QtConcurrent::map()

    QFuture<void> map(Sequence<T> &sequence, functor mapFunc)

    对容器sequence中每一个元素都执行mapFunc的并行操作,并且不需要返回结果。sequence是类似于QList之类的容器对象,mapFunc是参数与容器类型Sequence<T> 的T相同的函数。

    void square(int &n) { // 不返回,直接修改参数
        n = n * n;
    }
    
    int main() {
        QList<int> list = {1, 2, 3, 4, 5};
    
        QtConcurrent::map(list, square);
    	for (int i : list) qDebug() << i;
    
        return 0;
    }
    
  3. QtConcurrent::mapped()

    QFuture<Sequence<T>> map(Sequence<T> &sequence, functor mapFunc)

    与QtConcurrent::map()类似,但其可以有返回值,返回一个包含各返回值的新容器。

    int square(int n) { // 返回结果
        return n * n;
    }
    
    int main() {
        QList<int> list = {1, 2, 3, 4, 5};
    
        QFuture<QList<int>> future = QtConcurrent::mapped(list, square);
        future.waitForFinished();
        
        QList<int> result = future.result(); // 获取任务运行结果
        for (int i : result) qDebug() << i;
    
        return 0;
    }
    
  4. QtConcurrent::mappedReduced()

    QFuture<Sequence<T>> mappedReduced(Sequence<T> &sequence, functor mapFunc, functor reduceFunc);

    与QtConcurrent::mapped()功能类似,在QtConcurrent::mapped()基础上增加了一个reduceFunc函数,这个方法不再返回返回值构成的新容器,而是利用reduceFunc函数将获取到的返回值进行处理。

    int square(int n) {
        return n * n;
    }
    
    void sum(int &result, const int &value) {
        result += value;
    }
    
    int main() {
        QList<int> list = {1, 2, 3, 4, 5};
        QFuture<int> future = QtConcurrent::mappedReduced(list, square, sum);
        future.waitForFinished();
        qDebug() << "Sum of squares:" << future.result(); // sum()将各子任务的square()返回值统一处理
        return 0;
    }
    
  5. QtConcurrent::filter()

    QFuture<void> filter(Sequence<T> &sequence, functor filterFunc);

    可以参考map()去理解,它遍历sequence容器,应用过滤器filterFunc函数,只保留那些满足条件的元素。注意,其通过返回值来进行过滤。

    bool isEven(const int &n) {
        return n % 2 == 0;
    }
    
    int main() {
        QList<int> list = {1, 2, 3, 4, 5, 6};
        QtConcurrent::filter(list, isEven);
        for (int i : list) qDebug() << i; // 输出 2 4 6
        return 0;
    }
    
    
  6. QtConcurrent::filterped()

    QFuture<Sequence<T>> filterped(Sequence<T> &sequence, functor filterFunc);

    参考QtConcurrent::mapped()去理解,即允许由过滤后的返回值构成的新容器。

  7. QtConcurrent::filterpedReduced()

    QFuture<Sequence<T>> filterpedReduced(Sequence<T> &sequence, functor mapFunc, functor reduceFunc);

    参考QtConcurrent::mappedReduced()去理解,即对过滤后的返回值使用reduceFunc函数进行处理。

3.3 QFuture详解

  1. 获取结果方法:result()返回异步任务结果,也即函数的返回值。该函数会判断结果是否为可用的,如果不可用则阻塞等待。如果可用则直接把结果返回。
  2. 设置运行状态方法:cancel()取消任务执行,pause()暂停任务执行,resumes()恢复任务执行。不是所有任务都支持设置运行状态
  3. 获取运行状态方法: isCanceled()检查任务是否被取消,isStarted()检查任务是否开始,isFinished()检查任务是否完成,isRunning()检查任务是否正在运行,isPaused()检查任务是否被暂停。
  4. 获取任务进度信息方法:progressValue()返回任务当前进度,progressMinimum()返回任务进度的最大值,progressMaximum()返回任务进度的最小值。
  5. 其他:上述有些方法在run的时候不一定有用,就可以使用waitForFinished(),阻塞等待直至任务完成时或方法可用时。

4. 附C++方法std::thread的简单使用

C++自带的多线程方法也很简便,但因为目前我使用的不多,故我在这里只是简单介绍一下。

std::thread 在构造时会立即启动线程,在构造时就可以传递一个函数或一个可调用对象(如函数对象或 Lambda表达式)作为线程中需要执行的任务。

在构造完后,可以用以下两种方法选择线程运行模式:

  • join()
    等待线程完成并阻塞当前线程。如果你不 join 一个线程,在程序结束时,未 join 的线程可能会导致程序异常终止。
  • detach()
    将线程与主线程分离,允许它在后台独立执行。使用 detach() 后,主线程不再等待该线程。
#include <iostream>
#include <thread>

void square1(int n) { // 传参
    std::cout << "the square of number : " << n;
}

void square2(int &n) { // 引用传参
    n = n * n;
    std::cout << "the square of number : " << n;
}

int main() {
    int value = 2;
    
    std::thread t1(square1, value);  // 传参
    t1.join(); // 使用join等待线程结束
    
    std::thread t2(square2, std::ref(value));  // 通过std::ref传递引用
    t2.detach(); // 使用detach分离线程,后台运行
    return 0;
}