首页 > 其他分享 >Qt-线程和线程池

Qt-线程和线程池

时间:2024-07-14 16:54:45浏览次数:12  
标签:Qt 对象 子类 任务 线程 QThread 函数

前言

Qt实现在线程中执行任务有4种方法,分别是:
1. 创建一个派生于 QThread 类的子类,重写run函数,在run函数中执行任务
2. 创建一个派生于 QObject 的子类,调用 QObject::moveToThread() 方法将子类对象移动到子线程对象中。
3. 使用线程池QThreadPool
4. 使用QtConcurrent执行并行任务

这4种方法的完整代码示例在绑定的资源中,大家可以免费下载。



一 重写run函数的方法执行任务

局限性:只能在 run 函数中处理任务

使用步骤
1. 创建一个派生于 QThread 类的子类。

2. 在子类中重写 run 函数,并在 run 函数中编写子线程执行的任务代码。
    注意:不能在类的外部调用 run() 方法启动子线程,只能通过调用 start() 方法启动子线程,启动子线程后,子线程会自动执行 run 任务。

3. 在主线程中创建子线程对象,new 一个就行。

4. 在主线程中关联 QThread::finished  信号,用于在槽函数中清理子线程资源,释放内存。

5. 在主线程中关联子线程中的自定义信号,用于主线程和子线程之间的通信。

6. 调用 start() 方法启动子线程,执行 run 中的任务。

7. 子线程执行完毕释放内存,清理资源。

代码示例

1. 子线程类头文件:

	protected:
    // 重写 QThread 类的虚函数,在该函数中处理子线程任务
    void run();

signals:
    // 自定义信号, 用于和主线程通信
    void curNumber(int num);

2. 子线程类源文件:

// 重写 QThread 类的虚函数,在该函数中处理子线程任务
void SubThread::run()
{
    // QThread::currentThread():获取当前线程的 QThread 对象指针
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while(1)
    {
        // 发送自定义信号,向主线程中传递数据
        emit curNumber(num++);
        if(num == 10000000)
        {
            break;
        }

        //阻塞,让当前子线程暂停1秒
        QThread::sleep(1);
    }

    qDebug() << "run() 执行完毕, 子线程退出...";
}

3. 主线程的构造函数中:
	
	// QThread::currentThread():获取当前线程的 QThread 对象指针
    qDebug() << "主线程对象地址:  " << QThread::currentThread();

    // 创建子线程对象
    SubThread* subThread = new SubThread;

    // 将子线程的自定义信号和主线程中的 Lambda 表达式表示的槽函数关联,用于传递数据
    connect(subThread, &SubThread::curNumber, this, [=](int num)
    {
        // 在 label 控件中显示数字
        ui->label->setNum(num);
    });

    // 点击 startBtn 按钮,启动子线程
    connect(ui->pushButton, &QPushButton::clicked, this, [=]()
    {
        // 启动子线程,子线程自动执行 run 函数中的任务
        subThread->start();
    });


    //将子线程的信号和主线程的 Lambda 表达式表示的槽函数关联,用于在线程任务执行完成后清理子线程资源,释放内存
    // connect(subThread, &SubThread::finished, this, [&](){
    //     //释放子线程对象的堆内存
    //     delete subThread;
    // });

    //在子线程对象 subThread 的任务执行结束后,在 subThread 的事件循环结束后释放子线程对象 subThread 的内存
//注意:subThread 线程对象的 run 函数中需要启用事件循环(exec())。

    connect(subThread, &QThread::finished, subThread, &QObject::deleteLater);

二 移动子类对象到子线程对象

相比重写Run函数的优势:可以定义多个成员函数处理任务

使用步骤:

1. 创建一个派生于 QObject 的子类,在子类中创建自定义的公共成员函数(建议创建为槽函数,方便与子线程启动信号 QThread::started() 关联),用于执行子线程的任务(相当于 run 函数)。

2. 在主线程中实例化子线程对象(QThread) 和实例化上一步创建的子类对象。
    注意:千万不要给子类对象指定父对象

3. 调用 QObject::moveToThread() 方法将子类对象移动到子线程对象中。

4. 在主线程中将子线程对象的 QThread::started() 信号和子类对象中执行任务的槽函数关联,这样,在启动子线程时会自动执行任务

5. 在主线程中关联子线程中的自定义信号,用于主线程和子线程之间的通信。

6. 在主线程中关联 QThread::finished  信号,用于在槽函数中清理子线程和子对象资源,释放内存。

7. 调用 start() 函数启动子线程。
    注意:子线程启动后,移动到子线程中的子类对象并没有立即执行任务(自定义的公共成员槽函数)

8. 由于将子线程对象的 QThread::started() 信号和子类对象中执行任务的槽函数关联了,子线程启动后,子类对象中的任务也就自动执行了。

9. 子线程任务执行完毕释放子线程对象和子类对象内存,清理资源。

代码示例:

1. 子类对象头文件:

public slots:
    //创建自定义公共成员函数(建议创建为槽函数,方便与子线程启动信号 QThread::started() 关联),用于执行子线程任务
    void working();

signals:
    // 自定义信号, 用于和主线程通信
    void curNumber(int num);

2. 子类对象源文件:

// 创建自定义公共成员函数(建议创建为槽函数,方便与子线程启动信号 QThread::started() 关联),用于执行子线程任务
void MyWork::working() {

    // QThread::currentThread():获取当前线程的 QThread 对象指针
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while (1) {
        // 发送自定义信号,向主线程中传递数据
        emit curNumber(num++);
        if (num == 10000000) {
            break;
        }

        // 阻塞,让当前子线程暂停1秒
        QThread::sleep(1);
    }

    qDebug() << "run() 执行完毕, 子线程退出...";
}

3. 主线程的构造函数:

// QThread::currentThread():获取当前线程的 QThread 对象指针
    qDebug() << "主线程对象地址:  " << QThread::currentThread();

    // 创建线程对象
    QThread* sub = new QThread;

    // 创建工作的类对象
    // 注意:千万不要指定给创建的对象指定父对象
    MyWork* work = new MyWork;

    // 将工作的类对象移动到创建的子线程对象中
    work->moveToThread(sub);

    //将子线程对象的 QThread::started() 信号和子类对象中执行任务的槽函数关联,这样,在启动子线程时会自动执行任务
    connect(sub, &QThread::started, work, &MyWork::working);

    // 将子线程的自定义信号和主线程中的 Lambda 表达式表示的槽函数关联,用于传递数据
    connect(work, &MyWork::curNumber, this, [=](int num)
    {
        // 在 label 控件中显示数字
        ui->label->setNum(num);
    });

    // 点击 startBtn 按钮,执行子线程任务
    connect(ui->pushButton, &QPushButton::clicked, this, [=]()
    {
        // 启动线程
        sub->start();
    });

    //将子线程的 QThread::finished 信号和主线程的 Lambda 表达式表示的槽函数关联,用于在线程任务执行完成后清理子线程资源,释放内存
    // connect(sub, &QThread::finished, this, [&](){
    //     //释放子线程对象的堆内存
    //     delete sub;
    //     //释放工作类对象
    //     delete work;
    // });

        //在子线程对象 sub 的任务执行结束后,在子线程对象 sub 和子类对象 word 的事件循环结束后释放子线程对象 sub 和子类 work 的内存
注意:sub 的 run 函数中需要启用事件循环(exec())
    connect(sub, &QThread::finished, sub, &QObject::deleteLater);
    connect(sub, &QThread::finished, work, &QObject::deleteLater);


三 使用线程池QThreadPool

QThreadPool说明:

1. Qt 中的 QThreadPool 类管理了一组线程, 里边还维护了一个任务队列。
2. 每个 Qt 应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问它。也可以单独创建一个 QThreadPool 对象使用。
3. 线程池中的线程的任务执行完毕,该线程不会立即结束,而是由线程池自动管理该线程是挂起、继续执行任务还是释放等。

 常用成员函数: 

1. 获取 Qt 应用程序的全局线程池对象指针
static QThreadPool * QThreadPool::globalInstance();


2. 线程池中的线程个数

2.1 获取线程池中的最大活动线程个数
int maxThreadCount() const;

2.2 设置线程池中的最大活动线程个数
//说明:无论提交到该线程池的任务数量有多少,同时执行的任务将不会超过设置的最大活动线程数。
void setMaxThreadCount(int maxThreadCount);

2.3 获取线程池中正在工作的线程个数
int QThreadPool::activeThreadCount() const;


3. 向线程池中添加任务(QRunnable 类型)

3.1 如果线程池中没有空闲的线程了, 任务会放到任务队列中, 等待线程处理
//参数1:任务对象指针
//参数2:指定任务的执行优先级。默认值为0,表示正常优先级。数值越大,优先级越高。
void QThreadPool::start(QRunnable * runnable, int priority = 0);

3.2 如果线程池中没有空闲的线程了, 直接返回, 任务添加失败, 任务不会添加到任务队列中
bool QThreadPool::tryStart(QRunnable * runnable);


4. 删除线程池中的任务

4.1 尝试将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了
bool QThreadPool::tryTake(QRunnable *runnable);

4.2 将线程池中的任务队列中所有未开始处理的任务删除, 如果任务已经开始执行就无法删除了
void QThreadPool::clear();

使用步骤: 

1. 创建派生于 QRunnable 的任务子类。

 1.1 重写 run 函数,在 run 函数中编写任务的执行代码。
 1.2 在任务子类的构造函数中设置任务对象在执行完毕后自动销毁。

2. 如果想在任务中使用 Qt 的信号槽机制进行数据传递,那任务子类除了继承 QRunnable 类外,还需要继承 QObject 类。

3. 获取 Qt 应用程序的全局线程池对象指针。

4. 设置线程池中的最大活动线程个数。

5. 创建任务对象,并将其添加到线程池中。


四 使用QtConcurrent执行并行任务 

说明:这个模块极大的简化了Qt线程的使用,我们只需要调用它的API,在应用程序的全局线程池对象中安排执行某个函数而无需关心线程的创建管理和释放

代码示例:

举例1:在线程池中的线程中执行的无返回值无参数函数
void Fun1()
{
    qDebug() << "使用 QtConcurrent::run 执行无返回值无参数函数测试...";
}

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


    // 使用 lambda 表达式在 QtConcurrent::run() 中调用要执行的 Fun1() 函数
    QFuture<void> future1 = QtConcurrent::run([](){
        Fun1();
    });

    // 等待线程池中的 Fun1() 函数执行完毕
    future1.waitForFinished();

}

举例2:在线程池中的线程中执行的有返回值有参数函数
int Fun2(int number) {
    return number * number;
}

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


    // 使用 lambda 表达式包装有返回值的函数(传递参数5)
    QFuture<int> future2 = QtConcurrent::run([](int num){
        return Fun2(num);
    }, 5);

    // 等待线程池中的 doSomethingUseful() 函数执行完毕
    future2.waitForFinished();

    // 获取函数的返回值
    int result = future2.result();

    qDebug() << "The square of 5 is:" << result;

}

标签:Qt,对象,子类,任务,线程,QThread,函数
From: https://blog.csdn.net/qq_36318563/article/details/140364426

相关文章

  • Linux系统编程-线程同步详解
    线程同步是指多个线程协调工作,以便在共享资源的访问和操作过程中保持数据一致性和正确性。在多线程环境中,线程是并发执行的,因此如果多个线程同时访问和修改共享资源,可能会导致数据不一致、竞态条件(racecondition)等问题。线程同步通过协调线程的执行顺序和共享资源的访问来避免......
  • 【tomcat】Tomcat如何扩展Java线程池原理
    池化技术在后端中,对于经常使用池化就是来提升系统性能,比如数据库连接池、线程池连接池等,本质都是利用空间换时间的来提升性能,用来避免资源的频繁创建和销毁,以此提高资源的复用率,所以合理设置系统所需的线程池大小非常重要,一般都需要结合线程启动监控系统来观察,查看设置的是......
  • Java优雅使用线程池连接SFTP进行文件上传下载 解决请求量大问题
    Java优雅使用线程池连接SFTP进行文件上传下载解决请求量大问题使用FTP连接池降低资源消耗,提高响应速率为什么要使用线程池连接SFTP呢?在Java中使用线程池来连接SFTP(SecureFileTransferProtocol)工具的原因主要与性能、资源管理和效率有关。以下是一些关键原因:资源管......
  • 【Linux】多线程_3
    文章目录九、多线程3.C++11中的多线程4.线程的简单封装未完待续九、多线程3.C++11中的多线程Linux中是根据多线程库来实现多线程的,C++11也有自己的多线程,那它的多线程又是怎样的?我们来使用一些C++11的多线程。Makefile:testThread:testThread.cc g++-o$@......
  • C++客户端Qt开发——开发环境
    一、QT开发环境1.安装三个部分①C++编译器(gcc,cl.exe……)②QTSDKSDK-->软件开发工具包比如,windows版本QTSDK里已经内置了C++的编译器(内置编译器是mingw,windows版本的gcc/g++)③QT的集成开发环境(IDE)官方提供的QTCreator最容易入门,最容易上手的方式,开箱即用,虽然QTCrea......
  • 【Qt 信号和槽】一篇文章带你详细的了解 Qt 的信号与槽
    文章目录1.信号和槽的基本概念2.`connect()`函数的用法......
  • python进程和线程_day013
    python进程和线程概念相关进程概览线程概览Python中的多进程Python中的多线程多进程还是多线程单线程+异步I/O(协程)应用案例示例1:将耗时间的任务放到线程中以获得更好的用户体验示例2:使用多进程对复杂任务进行“分而治之”。今天我们使用的计算机早已进入多CPU或多核......
  • Qt-事件过滤器、事件分发器、事件处理器
    前言Qt中事件的处理步骤1.当事件产生之后,Qt应用程序对象通过调用QApplication::notify()函数将事件发送到指定的窗口。2.事件在发送过程中可以通过向对象(窗口、按钮等)安装事件过滤器QObject::eventFilter()来对事件进行过滤。Qt应用程序默认不对任何产生的事件......
  • MQTT是什么,物联网
    写文思路:以下从几个方面介绍MQTT,包括:MQTT是什么,MQTT和webSocket的结合,以及使用场景,一、MQTT是什么MQTT(MessageQueuingTelemetryTransport)是一种轻量级的发布/订阅消息传输协议,广泛应用于物联网(IoT)设备之间的通信。MQTT在设计时考虑了低带宽、不可靠网络环境下的高效......
  • Java中的多线程详解
    Java中的多线程详解大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!多线程编程是Java开发中一个非常重要的主题。在多线程环境下,程序可以同时执行多个任务,从而提高程序的执行效率。本文将详细介绍Java中的多线程,包括线程的创建、线程的生命周期、线程的同......