QT实现多线程的方法

ops/2025/2/8 11:35:12/

目录

QThread%E7%B1%BB-toc" name="tableOfContents" style="margin-left:0px">一、继承QThread

1)自定义线程类

2)使用自定义的子线程类

3)使用说明

QThread%E7%9A%84%E6%B6%88%E6%81%AF%E5%BE%AA%E7%8E%AF-toc" name="tableOfContents" style="margin-left:0px">二、利用QThread的消息循环

1)自定义执行类

2)启动子线程

3)方法说明 

三、使用线程池

四、方法比较


        QT多线程编程常见的有3种实现方法,一种是继承QThread类,一种是利用QThread的消息循环,还有一种是使用线程池。这两种方式在不同的场景下各有优势,下面对两种实现方法进行详细说明。

QThread%E7%B1%BB" name="%E4%B8%80%E3%80%81%E7%BB%A7%E6%89%BFQThread%E7%B1%BB">一、继承QThread

        直接继承QTread类是进行多线程编程最常用的一种方式。我们只需要定义一个自己的类,并继承自QThread即可。具体实现如下所示。

1)自定义线程类

        首先我们在头文件中自定义一个类MyThread,在类中定义好构造函数、析构函数和run函数。run函数必须要重写,用于实现子线程需要完成的工作。当子线程启动时,将直接执行run函数中的代码,也只有run函数中的代码是在子线程中执行的。在我们自定义的MyThread类中,我们同样可以定义公共函数,比如setPara(),用于在启动子线程之前,为子线程对象设置参数信息。同时也可以定义信号,用于子线程向外传递参数。在类MyThread的对象中,虽然run函数中的代码运行在子线程中,但run函数依然可以访问对象中的各类成员。但当run函数访问对象中的公有成员时,若创建该子线程对象的线程也需要访问该公有成员,则可能会存在访问冲突的问题,这需要在具体实现时加以注意,避免多个线程同时访问同一内存。

       类MyThread的CPP文件实现不再赘述,与所有类的实现一样。

mythread.h头文件#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>
#include <QObject>class MyThread : public QThread
{Q_OBJECT
public:MyThread (QObject *parent = nullptr);~MyThread ();
protected:void run();//重写该函数,实现子线程需要完成的工作
private:int a,b,c;
public:void setPara(int,int,int);
signals:void sendValue(int);
};
#endif // MYTHREAD_H

2)使用自定义的子线程类

       使用继承自QThread的类也非常简单。首先在我们需要使用的地方,包含自定义类的头文件mythread.h,然后使用该类实例化一个对象,在启动线程前,可以调用该对象的公有函数设置一些参数,最后调用对象的start()函数启动线程。如果需要从子线程中传递出参数,则可以使用信号和槽将子线程对象的信号与当前的槽函数连接。

3)使用说明

        需要指出的是,调用对象的start()函数后,将默认自动执行run函数中的代码。run函数执行完成后,线程将自动退出,子线程中不会运行消息循环机制。你可以多次调用start()函数,前提是线程已经成功退出,否则run函数不会执行。因此,需要重启子线程前,你需要判断前一次启动的线程是否真的已经成功退出(if (!mythread.isRunning()) { thread.start(); } else { thread.wait(); })。你确实需要这么做,因为真正结束子线程是操作系统来控制的,run函数执行完成只代表它的工作已经完成,但子线程的资源未必立即释放。

       在下面的例子中,我们直接在main函数中定义了子线程类,并启动了子线程,也就是我们直接在程序的主线程中创建了一个子线程。那么这个子线程对象的所有资源其实都属于主线程,我们可以在主线程中通过子线程对象myThread访问到该对象的所有公有成员。对象myThread不属于子线程,属于子线程的资源是在run函数中定义的对象。

#include "mythread.h"
#include <QApplication>
int main(int argc, char *argv[])
{QApplication a(argc, argv);MyThread myThread;myThread.setPra(1,2,3);myThread.start();return a.exec();
}

QThread%E7%9A%84%E6%B6%88%E6%81%AF%E5%BE%AA%E7%8E%AF" name="%E4%BA%8C%E3%80%81%E5%88%A9%E7%94%A8QThread%E7%9A%84%E6%B6%88%E6%81%AF%E5%BE%AA%E7%8E%AF">二、利用QThread的消息循环

        有时候我们可能不能确定何时需要启动子线程,而是要根据任务需要由程序类决定是否需要使用子线程。在这种情况,我们可以利用QThread的消息循环机制来实现,具体如下是QT帮助文档提供的示例,下面对示例代码进行逐一解释。

1)自定义执行类

       首先我们定义了一个需要在子线程中执行的类,该类完全由用户自定义,继承自基类QObject。然后在类中实现我们需要执行的业务操作函数,该函数必须定义为共有的槽函数,因为它需要由外部连接触发。我们也同样可以定义信号,向外部传递信息。

2)启动子线程

        在需要使用子线程的地方,首先创建一个QThread对象workerThread,这就是一个基本的QThread对象,然后再创建一个前面自定义的执行类对象worker,随后调用对象worker的moveToThread()函数,将worker移入到workerThread中,再连接信号和槽,最后调用workerThread的start函数启动子线程。

  class Worker : public QObject//定义一个需要在子线程中运行的类{Q_OBJECTpublic slots:void doWork(const QString &parameter) {//在子线程中需要执行的函数QString result;emit resultReady(result);}signals:void resultReady(const QString &result);//对外发送的信号};//以下是如何使用子线程class Controller : public QObject{Q_OBJECTQThread workerThread;//创建一个QThread 对象public:Controller() {Worker *worker = new Worker;//创建一个需要在子线程中运行的对象worker->moveToThread(&workerThread);//将子线程中运行的对象移入到workerThread对象中connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);//线程结束后删除对象connect(this, &Controller::operate, worker, &Worker::doWork);//链接外部信号,执行子线程的工作connect(worker, &Worker::resultReady, this, &Controller::handleResults);workerThread.start();//启动子线程,默认启动消息循环机制}~Controller() {workerThread.quit();//退出子线程workerThread.wait();}public slots:void handleResults(const QString &);signals:void operate(const QString &);};

3)方法说明 

        从前面的示例代码中我们可以发现,执行类Worker就是一个普通的类,它没有任何与线程相关的特殊定义或设置。moveToThread()函数是关键,它是QObject基类的成员函数,因此所有继承他的类的对象都可以调用该函数。示例代码中3个信号和槽连接,前两个是必要的,最后一个根据需要可以使用。当然,如果执行类Worker中有多个槽函数实现不同的业务工作,我们可以在添加连接更多的信号和槽。

       子线程的启动我们直接调用了start()函数,这里的启动机制与前述有所不同,它将直接创建子线程,并在子线程中运行消息循环机制,而不是运行run函数,这里我们也没有实现run函数。子线程将一直存在,直至我们调用quit()函数,或者主程序结束。即使在主程序退出前,也建议调用quit()函数,如上述示例代码所示,以确保成功释放资源。

       由于子线程一直存在,并运行着自己独立的消息循环,当上述代码中第二个连接的信号触发后,Worker类的dowork()槽函数就会执行,而它就是在子线程中执行的,并不是在创建Worker类对象的线程中(如主线程)。需要指出的是,虽然Worker类的dowork()槽函数在子线程中执行,但是Worker类的对象worker资源同样不属于子线程,而是创建它的线程。子线程只负责运行代码,访问资源,但并不管理对象worker的资源。

三、使用线程池

        首先看一下官方介绍:
        QThreadPool管理和重新设置单个QThread对象,以帮助降低使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用globalInstance()访问该对象。要使用QThreadPool线程,请子类QRunnable并实现run()虚拟函数。然后创建该类的一个对象并将其传递给QThreadPool::start()。

        QT提供的线程池,使QT的线程使用更加方便灵活。官方示例代码如下,我们只需要子类化QRunnable,定义执行类,然后重写run函数,再将执行类的对象传递给QThreadPool::globalInstance()->start(hello)即可。

         这里使用的是Qt应用程序的全局QThreadPool对象,我们当然也可以定义一个自己的QThreadPool *threadPool对象,便于对线程池一些参数的设置。比如我们可以设置线程池中最大的线程数:threadPool->setMaxThreadCount(20)。

  class HelloWorldTask : public QRunnable{void run() override{qDebug() << "Hello world from thread" << QThread::currentThread();}};HelloWorldTask *hello = new HelloWorldTask();QThreadPool::globalInstance()->start(hello);

四、方法比较

        QT提供的3中多线程实现方法可根据需要选择。对于第一种直接继承QThread类, 它可以适用于需要长时间持续存在的,或者有阻塞机制的业务中。比如我们要用QT实现一个网络数据接收套接字程序,我们需要持续监听端口,子线程需要阻塞,这时就可以使用这种方式。对于第二种QThread消息循环机制,在我们需要频繁执行相同业务,但又不能事先确定何时调用时可以使用,比如处理较大的网络数据包时,可能因为网络数据包很大,处理时间较长,我们不愿交给主线程去做,这时就可以通过这种方式让子线程来处理。当收到数据包后,利用信号和槽机制,调用子线程中的参函数处理数据。第三种线程池的方式则更加灵活,通常在处理大批量任务时比较适用,比如在并行计算中。总体而言,任何子线程业务工作都可以通过上述三种方式来实现,其区别主要是程序的开发便捷度和资源的优化利用上。

        最后需要指出的是,在子线程中我们应慎重使用无限循环,否则必须要为子线程的退出设置终止条件。


http://www.ppmy.cn/ops/156713.html

相关文章

《DeepSeek R1:7b 写一个python程序调用摄像头获取视频并显示》

C:\Users\Administrator>ollama run deepseek-r1:7b hello Hello! How can I assist you today? &#x1f60a; 写一个python程序调用摄像头获取视频并显示 好&#xff0c;我需要帮用户写一个Python程序&#xff0c;它能够使用摄像头获取视频&#xff0c;并在屏幕上显示出…

CSS 伪类(Pseudo-classes)的详细介绍

CSS 伪类详解与示例 在日常的前端开发中&#xff0c;CSS 伪类可以帮助我们非常精准地选择元素或其特定状态&#xff0c;从而达到丰富页面表现的目的。本文将详细介绍以下伪类的使用&#xff1a; 表单相关伪类 :checked、:disabled、:enabled、:in-range、:invalid、:optional、…

Selenium记录RPA初阶 - 基本输入元件

防止自己遗忘&#xff0c;故作此为记录。 爬取网页基本元件并修改后爬取。 包含元件&#xff1a; elements: dict[str, str] {"username": None,"password": None,"email": None,"website": None,"date": None,"ti…

移动云电脑轻松搭建DeepSeek本地大模型

本文带您在移动AI云电脑上部署DeepSeek-R1大模型&#xff0c;构建个人知识库&#xff0c;开启本地大模型使用之旅。 目前你可以在移动AI云电脑公众版、行业型&#xff08;GPU型&#xff09;云电脑中&#xff0c;利用Ollama来本地部署Deekseek R1模型。 首先选购一台移动AI云电…

五十天精通硬件设计第四天-场效应管知识及选型

场效应管(FET,Field-Effect Transistor)是一种利用电场效应控制电流的半导体器件,广泛应用于放大、开关等电路中。以下是场效应管的基本知识及选型要点: 一、场效应管的基本知识 1. 类型: - **结型场效应管(JFET)**: - N沟道和P沟道两种类型。 - 栅极与…

深入理解Linux命令 `autom4te`

autom4te 是 GNU Autotools 中的重要工具&#xff0c;用于生成 configure 脚本。它是 autoconf 的核心组件之一&#xff0c;负责管理宏处理和文件生成过程。本文将深入理解 autom4te 的工作机制、常用选项和实际应用。 autom4te 的工作机制 autom4te 作为 autoconf 的底层引擎…

系统调用-常用IO函数

系统调用 系统调用概述 如果想操作内核(kernel)&#xff0c;需要调用内核的系统调用(system calls) 系统调用有三种方式: shell&#xff0c; 用户通过shell命令&#xff0c;有shell解释器操作内核的系统调用 库函数&#xff0c;用户通过 应用层库函数 的接口&#xff0c;比…

使用动态协议包,实现客户端与服务器端

思维导图 使用链表记录接受的值 resver.c(服务器) #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.…