> 文章列表 > QT线程的故事

QT线程的故事

QT线程的故事

  本文介绍了Qt中四种创建线程方式,分别是子线程继承QThread,使用moveToThread,使用QtConcurrent和使用QRunnable和QThreadPool。每种方式都有其优缺点和适用场景,开发者可以根据自己的需求和喜好选择合适的方式来实现多线程编程。希望本文对你有所帮助。

QT创建线程的几种方式

    • 一、子线程继承QThread
    • 二、使用moveToThread
    • 三、使用QtConcurrent
    • 四、使用QRunnable和QThreadPool
    • 五、线程回收
    • 参考链接

一、子线程继承QThread

  这是Qt中最基础的创建线程的方式,也是最接近原生操作系统API的方式。这种方式的优点是简单直接,可以方便地控制线程的生命周期和状态。缺点是不够灵活,每个子线程都需要创建一个新的对象,并且不能在run()函数中使用信号槽机制
  使用start()函数启动线程,quit()或exit()函数结束线程,wait()函数等待线程结束并回收资源。具体使用请见下面的代码

#include <QThread>
#include <QDebug>
#include <iostream>class MyThread : public QThread//继承QThread,必不可少
{Q_OBJECT
public:MyThread(QObject *parent = nullptr) : QThread(parent) {}
protected:void run() //重写run函数{// 线程要执行的任务for (int i = 0; i < 10; i++){qDebug() << "This is subThread::" << QThread::currentThreadId();//输出当前线程IDQThread::sleep(1); // 模拟耗时操作}}
};main(){MyThread thread1 = new MyThread();MyThread thread2 = new MyThread();qDebug() << "This is MainThread::" << QThread::currentThreadId();thread1.start();  //启动thread1线程thread2.start();  //每个子线程都需要创建一个新的类thread1.quit();   //结束线程thread1thread2.quit();
}

二、使用moveToThread

  这是Qt中推荐使用的创建线程的方式,moveToThread可以将任何继承自QObject的类对象移动到一个新创建的子线程对象中,并在该子线程中执行该对象的槽函数。
  使用start()函数启动线程,quit()或exit()函数结束线程,wait()函数等待线程结束并回收资源。具体使用请见下面的代码

#include <QThread>
#include <QDebug>
#include <iostream>class MyThread : public QObject//继承QObject的类
{Q_OBJECT
public:MyThread(QObject *parent = nullptr) : QThread(parent) {}
protected:void taskFunc() // 线程要执行任务的函数{for (int i = 0; i < 10; i++){qDebug() << "This is subThread::" << QThread::currentThreadId();//输出当前线程IDQThread::sleep(1); // 模拟耗时操作}}
};main(){MyThread obj_1 = new MyThread();MyThread obj_2 = new MyThread();QThread thread = new QThread();obj_1->moveToThread(thread);obj_2->moveToThread(thread);qDebug() << "This is MainThread::" << QThread::currentThreadId();connect(thread, &QThread::started, obj_1, &MyThread::taskFunc);connect(thread, &QThread::started, obj_2, &MyThread::taskFunc);//两个对象使用一个线程执行时是串行执行。thread->start();//线程开始执行
}

三、使用QtConcurrent

  需要在qt项目文件中添加QtConcurrent模块,并且包含QtConcurrent头文件。此方法可以在对原程序架构改动较小的情况下实现子线程。既可以对普通函数使用也可以对类对象的方法使用。可以调用成员函数,也可以直接运行 Lambda 表达式,返回数据类型为QFuture。具体使用请见下面的代码。

// 调用成员函数
// 不带参数
QFuture<void> mf1 = QtConcurrent::run(this, &MainWindow::myFunc1);
mf1.waitForFinished(); // 阻塞等待
// 带参数
QFuture<void> mf2 = QtConcurrent::run(this, &MainWindow::myFunc2, QString("aaa"));
mf2.waitForFinished(); // 阻塞等待
// 带返回值
QFuture<int> mf3 = QtConcurrent::run(this, &MainWindow::myFunc3, 4, 5);
mf3.waitForFinished(); // 阻塞等待
qDebug() << "mf3" << QThread::currentThreadId() << mf3.result() << endl;// 调用 Lambda 表达式
// 不带参数
QFuture<void> lf1 = QtConcurrent::run([](){qDebug() << "lambda1" << QThread::currentThreadId();
});
lf1.waitForFinished(); // 阻塞等待
// 带参数
QFuture<void> lf2 = QtConcurrent::run([](QString str){qDebug() << "lambda2" << QThread::currentThreadId() << str;
}, QString("bbb"));
lf2.waitForFinished(); // 阻塞等待
// 带返回值
QFuture<int> lf3 = QtConcurrent::run([](int i, int j){int ret = i * j;qDebug() << "lambda3" << QThread::currentThreadId() << ret;return ret;
}, 6, 7);
lf3.waitForFinished(); // 阻塞等待
qDebug() << "lf3" << QThread::currentThreadId() << lf3.result() << endl;

四、使用QRunnable和QThreadPool

  这种方式创建线程的优点是灵活高效,可以自动管理线程池和资源分配,也可以设置线程池的大小和策略,还可以在多个线程之间共享资源,提高程序的性能。缺点是不能在任务类中使用信号槽机制,也不能获取函数的返回值或者监控函数的执行状态。

五、线程回收

  线程可以让程序并发地执行多个任务,提高程序的效率和响应性。然而,线程的创建和销毁也是需要消耗系统资源的,如果不及时回收线程,就会造成以下危害:
  1、占用系统资源,导致内存泄漏和性能下降。
  2、影响程序的稳定性和可靠性,可能导致线程被意外终止或者无法正常结束。
  3、增加程序的复杂度和难度,可能导致线程安全问题和同步问题。
  因此,创建线程后应该及时回收,或者使用线程池或者其他高级的线程管理方式来避免不必要的开销和风险。

参考链接

Qt 之 Concurrent 4、【例】并发函数Run用法示例
Qt线程的四种创建方式