真正去理解Qt5的多线程:手把手创建一个多线程控制台程序(使用movetothread方法)后附百度网盘下载地址

本文主要是介绍真正去理解Qt5的多线程:手把手创建一个多线程控制台程序(使用movetothread方法)后附百度网盘下载地址,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

了很多网上关于Qt多线程的应用实例文章,没有几篇真正讲明白Qt多线程原理的。后来发现知乎上有个叫“万丈高楼平地起”的作者写的两篇:
《Qt 多线程编程之敲开 QThread 类的大门》 https://zhuanlan.zhihu.com/p/53270619
《Qt 中的多线程技术》 https://zhuanlan.zhihu.com/p/52612180
再加上官方的说明文档和例程,基本搞清楚了,在官方例程的基础上进行了修改,也供各位Qt学习者参考。本文也参考了以上两篇文章的部分内容。针对官方推荐的第二中方法(moveToThread)实现多线程。

重点概念

先讲几个重点。详细理论请参考以上两篇文章:

(1) QThread类继承于QObject。一个QThread实例管理程序中的一个线程。QThread的执行开始于run()。run函数默认调用exec()实现事件循环。将自己的代码实现放到run函数中会让run函数变得臃肿庞大,后续难以维护,也不利于面向对象开发。采用重写run函数时,如果忘了在 run() 函数中调用 exec(),则没有事件循环了,执行完run就结束线程了。
(2)QObject 中的 moveToThread() 函数可以在不破坏类结构的前提下依然可以在新线程中运行。你可以使用QObject::moveToThread()将自定义的类对象(Worker类)移动到线程容器(即QThread类的一个实例)中使用。通过线程容器中的方法以及Qt的信号-槽机制实现对Worker类的访问和控制。

在这里插入图片描述

(3)controller、worker 对象到底在哪个线程?「在哪创建就属于哪」这句话放在任何地方都是适用的。而 moveToThread() 函数的作用是将槽函数在指定的线程中被调用。也就是说controller、worker对象均在主线程中。除了与controller绑定的槽函数(包括槽函数体里调用的函数)外,worker的其余函数也在主线程中执行。

实验目的

本次实验中首先在main函数中实现了一个简单的命令交互终端(while循环,用户可以发送命令控制各个线程的启动start、停止stop和检查check。在main函数中创建两个线程容器(controller1和controller2),每个线程中需要做的具体事情由on_doSomething()函数实现。(本次实验中,要做的事就是不停地让变量count累加1)。

实验过程

(1)创建 基于QObject 的子类 Worker,这个类有个成员函数 on_doSomething(),假如该函数中运行的代码非常耗时。因此需要将这个类对象“移动”到新线程里。这里将on_doSomething放到Workerloop函数中的while()中。通过执行startWorker函数,启动循环体。这样“”耗时工作“” 就在新线程中运行了。Workerloop中的循环体会根据成员变量isStop值来判断是否中止。
Worker 类需要有个槽函数用于执行外界的命令,还需要有个信号来向外界发送结果。在主线程里有个 signal 信号来关联并触发startWorker函数(这里没有使用信号-槽机制改变isStop的值,而是直接在主线程中调用closeWorker函数)。与此同时 Worker 类中有个 signal 信号(resultReady)用于向外界发送运行的结果(本次实验中main函数中存在命令交互while(),因此运行过程中无法实时显示,只能在命令交互结束后显示,因此没有使用)。如下列代码:

#ifndef WORKER_H
#define WORKER_H
#include <QObject>class Worker : public QObject
{Q_OBJECT
public:explicit Worker( QString name,QObject *parent = nullptr);
signals:void resultReady(const QString &str);//向外界发送结果,本实验中没有使用
public slots:void startWorker();void closeWorker();
private:void Workerloop();//事件循环void on_doSomething();//耗时操作
private:bool isStop;int count;QString m_name;
};
#endif // WORKER_H

为了体现是在不同线程中执行的,我们在各个函数中打印当前线程 ID。

#include "worker.h"
#include <QDebug>
#include <QThread>Worker::Worker(QString name,QObject *parent) : QObject(parent)
{m_name =  name;qDebug()<<"I'm "<< m_name <<" initialized in thread: "<<QThread::currentThreadId();isStop=false;count = 0;
}
void Worker::startWorker()
{qDebug()<<"I'm "<< m_name <<" startWorker in thread: "<<QThread::currentThreadId();isStop = false;Workerloop();
}
void Worker::closeWorker()
{qDebug()<<"I'm "<< m_name <<" closeWorker in thread: "<<QThread::currentThreadId();isStop = true;
}
void Worker::Workerloop()
{qDebug()<<"I'm "<< m_name <<" Workerloop in thread: "<<QThread::currentThreadId();while(!isStop){on_doSomething();QThread::sleep(1);}
}
void Worker::on_doSomething()
{count++;qDebug()<<"I'm "<< m_name <<" working in thread: "<<QThread::currentThreadId();/*   QString workmsg;workmsg.sprintf("conut=: %d",count);emit resultReady(workmsg);*/
}

(2)创建基于QObject 的Controller类,用来控制线程的开始和停止。在作为“外界”的 Controller 类中可以设计一个类似的启动Worker类的信号,但这里直接使用QThread::started()信号来代替。on_receivResult() 槽函数用于接收新线程的运行结果(没有使用)。Controller的槽对应着Worker的信号。

#ifndef CONTROLLER_H
#define CONTROLLER_H#include <QObject>
#include <QThread>
#include "worker.h"class Controller : public QObject
{Q_OBJECT
public:explicit Controller(int ctrlId,QString workname,QObject *parent = nullptr);~Controller();void start();void stop();
signals:    public slots:void on_receiveResult(const QString &str);
public:
bool isRunning;
private:QThread m_workThread;//线程容器。直接实例化,需要在析构函数中quit()+wait()Worker *m_worker;//线程中的对象 new在堆上,需用deleteLater().int m_ctrlId;QString m_workname;
};
#endif // CONTROLLER_H

QThread 类的实例通过调用 start() 函数发出的started信号就可以启动函数在新线程中运行。
正常的退出线程其实质是退出事件循环,即执行 exit(int returnCode = 0) 函数。返回0代表成功,其他非零值代表异常。quit() 函数等价于 exit(0)。线程退出后会发出 finished() 信号。

#include "controller.h"
#include <QDebug>
Controller::Controller(int ctrlId,QString workname,QObject *parent) : QObject(parent)
{m_ctrlId = ctrlId;m_workname = workname;qDebug()<<"Controller is running in main thread: "<<QThread::currentThreadId();isRunning = false;m_worker = new Worker(workname);m_worker->moveToThread(&m_workThread);connect(&m_workThread,&QThread::started,m_worker,&Worker::startWorker);connect(&m_workThread,&QThread::finished,m_worker,&QObject::deleteLater);connect(m_worker,&Worker::resultReady,this,&Controller::on_receiveResult);
}
Controller::~Controller()
{if(m_workThread.isRunning()){m_worker->closeWorker();m_workThread.quit();m_workThread.wait();}
}
void Controller::start()
{  m_workThread.start();isRunning = true;
}
void Controller::stop()
{if(m_workThread.isRunning()){m_worker->closeWorker();m_workThread.quit();m_workThread.wait();isRunning = false;}
}
void Controller::on_receiveResult(const QString &str)
{qDebug()<< str;
}

执行 Controller::~Controller()析构函数时,m_workThread对象会被销毁。但删除正在运行的QThread(即isFinished()返回false)将导致程序崩溃。因此增加 m_workThread.quit()和 m_workThread.wait();
(3)在main函数中,创建两个Controller类的实例,Id号分别为Thrd0和Thrd1;Controller类中的Worker类的初始化name分别为:“Tom"和"Jerry”。
在main函数中利用while启动一个终端交互,利用QTextStream 监测键盘输入的命令字:
exit 退出终端交互
start1 开启线程1
start2 开启线程2
stop1 退出线程1
stop2 退出线程2
check1 检查线程1是否运行
check2 检查线程2是否运行

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <controller.h>enum CTRID{Thrd0,Thrd1
};
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug()<<"The main threadID is: "<< QThread::currentThreadId();QString workername1("Tom");QString workername2("Jerry");Controller controller1(Thrd0,workername1);Controller controller2(Thrd1,workername2);qDebug()<<"you can enter the following commands by tapping the keyboard:\n";qDebug()<<"\tstart1\n\tstart2\n\tstop1\n\tstop2\n\tcheck1\n\tcheck2\n\teixt\n";QTextStream qin(stdin);QString cmd;while(1){qin>>cmd;qDebug()<<"I'm running in main: "<<QThread::currentThreadId();qDebug()<<"receive:"<<cmd<<endl;if(cmd=="exit"){controller1.stop();controller2.stop();qDebug()<<"main had exited.you can close this console. "<<endl;break;}if(cmd=="check1")qDebug()<<"controller1 status is: "<<controller1.isRunning<<endl;if(cmd=="check2")qDebug()<<"controller2 status is: "<<controller2.isRunning<<endl;if(cmd=="start1"){controller1.start();qDebug()<<"controller1 is started"<<endl;}if(cmd=="start2"){controller2.start();qDebug()<<"controller2 is started"<<endl;}if(controller1.isRunning==true){if(cmd=="stop1"){controller1.stop();qDebug()<<"controller1 is stoped"<<endl;}}if(controller2.isRunning==true){if(cmd=="stop2"){controller2.stop();qDebug()<<"controller2 is stoped"<<endl;}}}return a.exec();
}

看看执行效果:
在这里插入图片描述
启动后,在main函数中完成Controller的实例化和Worker的实例化,因此,Controller1、Controller2、Worker1和Worker2均在存在于主线程0x2524中。
在这里插入图片描述
分别输入“start1”、“start2”命令,可以看出,两个线程中的Worker分别工作在0x20e0和0x1cac两个线程中。
分别输入“stop1”、“stop2”命令,可以中止两个线程。
说明:中止线程后,再次输入start1、start2,虽然线程能启动,但controller无法执行构造函数,Worker无法启动,因此不能输出信息。

源代码已经上传,可免费下载。
https://download.csdn.net/download/SmartTiger_CSL/12158072
本来这个源代码资源是让大家免费下载的,可是CSDN平台强制改成了需要积分才能下载,因此,这里给出百度网盘免费下载地址
链接1:https://pan.baidu.com/s/1TWvURe7syAVBSaQd8EpTMg
提取码:6d7z
在这里插入图片描述
链接2:https://pan.baidu.com/s/1tq5oUv1e09VUCxEg1C4xMQ
提取码:qm4g

这篇关于真正去理解Qt5的多线程:手把手创建一个多线程控制台程序(使用movetothread方法)后附百度网盘下载地址的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/594986

相关文章

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

SQL中如何添加数据(常见方法及示例)

《SQL中如何添加数据(常见方法及示例)》SQL全称为StructuredQueryLanguage,是一种用于管理关系数据库的标准编程语言,下面给大家介绍SQL中如何添加数据,感兴趣的朋友一起看看吧... 目录在mysql中,有多种方法可以添加数据。以下是一些常见的方法及其示例。1. 使用INSERT I

Ubuntu如何分配​​未使用的空间

《Ubuntu如何分配​​未使用的空间》Ubuntu磁盘空间不足,实际未分配空间8.2G因LVM卷组名称格式差异(双破折号误写)导致无法扩展,确认正确卷组名后,使用lvextend和resize2fs... 目录1:原因2:操作3:报错5:解决问题:确认卷组名称​6:再次操作7:验证扩展是否成功8:问题已解

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

Python中反转字符串的常见方法小结

《Python中反转字符串的常见方法小结》在Python中,字符串对象没有内置的反转方法,然而,在实际开发中,我们经常会遇到需要反转字符串的场景,比如处理回文字符串、文本加密等,因此,掌握如何在Pyt... 目录python中反转字符串的方法技术背景实现步骤1. 使用切片2. 使用 reversed() 函

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

使用Docker构建Python Flask程序的详细教程

《使用Docker构建PythonFlask程序的详细教程》在当今的软件开发领域,容器化技术正变得越来越流行,而Docker无疑是其中的佼佼者,本文我们就来聊聊如何使用Docker构建一个简单的Py... 目录引言一、准备工作二、创建 Flask 应用程序三、创建 dockerfile四、构建 Docker

Python使用vllm处理多模态数据的预处理技巧

《Python使用vllm处理多模态数据的预处理技巧》本文深入探讨了在Python环境下使用vLLM处理多模态数据的预处理技巧,我们将从基础概念出发,详细讲解文本、图像、音频等多模态数据的预处理方法,... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核