Qt 多线程基础及线程使用方式-程序员宅基地

技术标签: Qt  qt  

Qt 多线程操作

应用程序在某些情况下需要处理比较复杂的逻辑, 如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作。这种情况下就需要使用多线程,其中一个线程处理窗口事件,其他线程进行逻辑运算,多个线程各司其职,不仅可以提高用户体验还可以提升程序的执行效率。

Qt中使用多线程需要注意:

  1. Qt的默认线程为窗口线程(主线程):负责窗口事件处理或窗口控件数据的更新;
  2. 子线程负责后台的业务逻辑,子线程不能对窗口对象做任何操作,这些事交给窗口线程;
  3. 主线程和子线程之间如果进行数据的传递,需要使用信号槽机制
2.线程类QThread

Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一共提供了两种创建子线程的方式。

类中常用API函数:

//QThead类常用 API
//构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
//判断线程中的任务是否处理完毕
bool QThread::isFinished() const;
//判断子线程是不是在执行任务
bool QThread::isRunning() const;

//Qt 可先设置线程优先级
//获取当前线程优先级
Priority QThread::priority() const;
//设置优先级
void QThread::setPriority(Priority priority);   //枚举类型



//退出线程的工作函数
void QThread::exit(int returnCode = 0);
//调用线程退出函数之后,线程不会马上退出,因为当前任务可能没有完成
//等待任务完成后退出线程,通常在exit() 后调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);

优先级:
QThread::IdlePriority --> 最低的优先级
QThread::LowestPriority
QThread::LowPriority
QThread::NormalPriority
QThread::HighPriority
QThread::HighestPriority
QThread::TimeCriticalPriority
QThread::InheritPriority --> 最高的优先级, 默认是这个

信号槽:

// 和调用 exit() 效果是一样的
// 代用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
[slot] void QThread::terminate();

// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();

静态函数

// 返回一个指向管理当前执行线程的QThread的指针
[static] QThread *QThread::currentThread();
// 返回可以在系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
// 线程休眠函数
[static] void QThread::msleep(unsigned long msecs);	// 单位: 毫秒
[static] void QThread::sleep(unsigned long secs);	// 单位: 秒
[static] void QThread::usleep(unsigned long usecs);	// 单位: 微秒

任务处理函数

// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();

run()是一个虚函数,如果想让创建的子线程执行某个任务,需要写一个子类让其继承QThread,并且在子类中重写父类的run()方法,函数体就是任务处理流程。

run()函数是受保护的成员函数,不能类外调用,如果需要通过当前线程对象调用槽函数start()启动子线程,启动后run()函数在线程内部被调用。

3.多线程使用:方式一

1.创建一个线程类的子对象,继承QThread

class MyThrad:public QThread
{
	......
}

2.重写父类的run()方法,在该函数内部编写子线程要处理的具体业务流程

class MyThread:public QThread
{
	protected:
	void run()
	{
		.........;
	}
}

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

MyThread * subThread = new MyThrad;

4.启动子线程,调用start()方法

subThread->start();

不能在类的外部调用run()方法启动子对象,在外部使用start()相当于run()

创建出子线程后,父子线程之间的通信可以通过信号槽的方式,注意:

Qt子线程对象不能操作窗口类型对象,只有主线程才能操作窗口对象。

5.释放线程资源,使用信号槽机制,窗口关闭时释放

connect(this,&QWidget::destroyed,this,[=](){
  //t1 为创建的子线程对象
  gen->quit();
  gen->wait();
  gen->deleteLater();
});
4.多线程使用:方式二

Qt提高的第二种线程的创建方式弥补了第一种的缺点==,使用更加灵活,单写起来相对复杂一些==。

具体操作步骤:

1.创建一个新的类,QObject派生

class Mywork :public QObject
{
	....;
}

2.类中添加一个公有的自定义成员函数,函数体就是子线程中执行的业务逻辑

class Mywork :public 	QObject
{
public:
	//自定义函数名、参数
	void working();
}

3.主线程中创建一个 QThread 对象,就是子线程对象

QThread  *sub = new QThread ;

4.在主线程中创建工作的类对象,不要给创建的对象指定父对象

// MyWork* work = new MyWork(this);    
Mywork* work = new Mywork;          // ok

5.将Mywork对象移动到创建的子线程对象中,需要调用QObject类提供的 moveToThread() 方法

/ 如果给work指定了父对象, 这个函数调用就失败了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub);	// 移动到子线程中工作s  

启动子线程,调用 start(), 这时候线程启动了,但是移动到线程中的对象并没有工作

调用 MyWork 类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的

6.释放线程资源,使用信号槽机制,窗口关闭时释放

//信号槽释放资源
connect(this,&QWidget::destroyed,this,[=](){
 	 //释放创建的子线程对象   
 	 ti->quit();
 	 t1->wait();
  t1->deleteLater();
  
  //释放创建的任务对象
  gen->deleteLater();
  
});
5.Qt 线程池的使用

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

线程池的组成:

**1.任务队列:**储存需要处理的任务,由工作的线程来处理这些任务

​ 通过线程池提供的API函数,将一个待处理的任务添加到任务列表,或者从任务列表队伍中删除,已处理的任务会被从任务队列中删除

​ 线程池的使用者,及往任务队列添加任务的线程就是生产者线程

2.工作的线程:(任务队列中的消费者),N个

​ 线程池中维护一定数量的工作线程,作用是不停的读任务队列,从里面取出任务并处理

如果任务队列为空,工作的线程将会被阻塞(使用条件变量/信号量阻塞)

​ 如果阻塞之后有了新的任务,由生产者将阻塞解除,工作线程开始工作

2.管理者线程:(不处理任务队列中的任务),1个

​ 作用:周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测

​ 任务过多时,适当创建新的工作线程

​ 任务过少时,适当销毁一些工作线程

QRunnable

使用线程池需要先创建任务,添加到线程池中的任务 需要QRunnable类型因此需要创建子类继承QRunnable类,然后重写run()方法,在该函数编写在线程池中执行的任务,并将子类对象传递给线程池,这样线程就可以被线程池中的某个工作线程处理掉。

QRunnable常用函数:

// 在子类中必须要重写的函数, 里边是任务的处理流程
[pure virtual] void QRunnable::run();

// 参数设置为 true: 这个任务对象在线程池中的线程中处理完毕, 这个任务对象就会自动销毁
// 参数设置为 false: 这个任务对象在线程池中的线程中处理完毕, 对象需要程序猿手动销毁
void QRunnable::setAutoDelete(bool autoDelete);
// 获取当前任务对象的析构方式,返回true->自动析构, 返回false->手动析构
bool QRunnable::autoDelete() const;

1.创建一个要添加到线程池中的任务类

class MyWork:public QObject , public QRunnable
{
	Q_OBJECT
public:
	explicit MyWork (QObject *parent = nullptr)
	{
		//执行任务完毕,该对象自动销毁
		setAutoDelete(true);
	}
~MyWork();

void run() override();
}

在上面的示例中 MyWork 类是一个多重继承,如果需要在这个任务中使用 Qt 的信号槽机制进行数据的传递就必须继承 QObject 这个类,如果不使用信号槽传递数据就可以不继承了,只继承 QRunnable 即可。

QThreadPool

Qt 中的 QThreadPool 类管理了一组 QThreads, 里边还维护了一个任务队列。QThreadPool 管理和回收各个 QThread 对象,以帮助减少使用线程的程序中的线程创建成本。==每个Qt应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问它。==也可以单独创建一个 QThreadPool 对象使用。

线程池常用API函数:

// 获取和设置线程中的最大线程个数
int maxThreadCount() const;
void setMaxThreadCount(int maxThreadCount);

// 给线程池添加任务, 任务是一个 QRunnable 类型的对象
// 如果线程池中没有空闲的线程了, 任务会放到任务队列中, 等待线程处理
void QThreadPool::start(QRunnable * runnable, int priority = 0);
// 如果线程池中没有空闲的线程了, 直接返回值, 任务添加失败, 任务不会添加到任务队列中
bool QThreadPool::tryStart(QRunnable * runnable);

// 线程池中被激活的线程的个数(正在工作的线程个数)
int QThreadPool::activeThreadCount() const;

// 尝试性的将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了
bool QThreadPool::tryTake(QRunnable *runnable);
// 将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除了
void QThreadPool::clear();

// 在每个Qt应用程序中都有一个全局的线程池对象, 通过这个函数直接访问这个对象
static QThreadPool * QThreadPool::globalInstance();

runnable);

// 线程池中被激活的线程的个数(正在工作的线程个数)
int QThreadPool::activeThreadCount() const;

// 尝试性的将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了
bool QThreadPool::tryTake(QRunnable *runnable);
// 将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除了
void QThreadPool::clear();

// 在每个Qt应用程序中都有一个全局的线程池对象, 通过这个函数直接访问这个对象
static QThreadPool * QThreadPool::globalInstance();


一般情况下,我们不需要在 Qt 程序中创建线程池对象,**直接使用 Qt 为每个应用程序提供的线程池全局对象即可。得到线程池对象之后,调用 start() 方法**就可以将一个任务添加到线程池中,这个任务就可以被线程池内部的线程池处理掉了,使用线程池比自己创建线程的这种多种多线程方式更加简单和易于维护。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_49730048/article/details/120791091

智能推荐

flask-login-程序员宅基地

文章浏览阅读170次。创建扩展对象实例from flask_login import LoginManagerlogin_manager = LoginManager()login_manager.login_view = 'auth.login'# 上面这一句是设置登录视图的名称,如果一个未登录用户请求一个只有登录用户才能访问的视图,# 则闪现一条错误消息,并重定向到这里设置的登录视图。# 如果未设置..._python flask please log in to access this page

html怎么控制top值为0,关于vue滚动scrollTop 赋值一直为0问题-程序员宅基地

文章浏览阅读428次。Vue中document.body.scrollTop的值总为零的解决办法最近在做vue的时候监听页面滚动发现document.body.scrollTop一直为0但是发现document.body.scrollTop一直是0。查资料发现是DTD的问题。页面指定了DTD,即指定了DOCTYPE时,使用document.documentElement。页面没有DTD,即没指定DOCTYPE时,使用d..._滚动给scrolltop赋值

kingbase数据库安装教程(初步使用)(人大金仓)-程序员宅基地

文章浏览阅读2.1k次,点赞25次,收藏21次。人大金仓数据库管理系统KingbaseES(简称:金仓数据库或KingbaseES)是北京人大金仓信息技术股份有限公司自主研制开发的具有自主知识产权的通用关系型数据库管理系统。_kingbase

vue基础笔试题_vue笔试题-程序员宅基地

文章浏览阅读1.2w次,点赞20次,收藏156次。ctions 选项用来定义事件处理方法,用于处理 state 数据。actions 类似于 mutations,不同之处在于 actions 是异步执行的,事件处理函数可以接收 {commit} 对象,完成 mutation 提交,从而方便 devtools 调试工具跟踪状态的 state 变化。..............._vue笔试题

isis协议配置和详解-程序员宅基地

文章浏览阅读1.1w次,点赞2次,收藏23次。isis是一种与ospf很相似的网络协议(属于动态路由协议),它被应用在巨大规模网络,如运营商以及银行等。同样的它也是基于链路状态算法,支持clnp网络,ip网络。与ospf不同的是,它是基于数据链路层报文传输,而ospf则是在ip层进行计算。它可以自动的发现远程网络,只要网络拓扑结构发生了变化,路由器就会相互交换路由信息,不仅能够自动获知新增加的网络,还可以在当前网络连接失败时找出备用路径。无类..._isis协议配置

Proxychains 手册_proxychains是什么-程序员宅基地

文章浏览阅读1.9k次。名称:Proxychains – 通过代理服务器进行连接语法:proxychains 描述:这个程序会强制所有使用特定tcp连接的客户端所引起的TCP连接走代理通道。它是一种跳板程序。这个软件和sockscap、premo、eborder异曲同工。2.0版支持SOCKS4、SOCKS5、HTTP类的代理。认证方法:socks-“user/pass”,http-“basic_proxychains是什么

随便推点

Oracle游标:处理查询结果集的好工具_oracle查询游标结果集-程序员宅基地

文章浏览阅读273次。通过显式游标和隐式游标,我们可以方便地在数据库程序中处理查询结果集,实现复杂的业务逻辑。_oracle查询游标结果集

计算机主机箱内的硬件设备主要有哪些,电脑主机有哪些硬件设备-程序员宅基地

文章浏览阅读5.6k次。电脑主机有哪些硬件设备导语:在台式电脑,我们能看到的最基本的硬件就是一个显示器和主机,然后还包括键盘、鼠标。这样一个基本的电脑就完全了。以下是小编精心整理的电脑硬件知识,希望对您有所帮助。主机上面又分为以下几种硬件:1、主板;主板在主机上是一个板块,它是将电脑上的其他硬件连接在一起,最后在电脑启动的时候,主板上的信息就会传输到电脑上显示出来。2、cpu,cpu处理器的话可能你也已经有所了解了。其实..._主机的硬件设备有哪些

Oracle触发器原理、创建、修改、删除_用oracle创建一个instead of触发器,当在course表中删除数据,不允许在course-程序员宅基地

文章浏览阅读3.7k次,点赞2次,收藏7次。本篇主要内容如下:8.1 触发器类型8.1.1 DML触发器8.1.2 替代触发器8.1.3 系统触发器8.2创建触发器8.2.1 触发器触发次序8.2.2 创建DML触发器8.2.3 创建替代(INSTEAD OF)触发器8.2.3 创建系统事件触发器8.2.4 系统触发器事件属性8.2.5 使用触发器谓词8.2.6 重新编译触发器8.3删除和使用触发器8.4触发器和数据字典8.5数据库触发器的应用举例8.6 触发器的查看8...._用oracle创建一个instead of触发器,当在course表中删除数据,不允许在course表

计算机科学与技术网上书店,计算机科学与技术毕业论文:基于web的网上书店.doc...-程序员宅基地

文章浏览阅读188次。本科毕业论文(设计)题  目  基于web的网上书店学生姓名专业名称  计算机科学与技术指导教师目录1、引言52、系统概述62.1概述62.2 开发平台73.需求分析73.1总体需求描述73.2系统总体功能图73.3系统需要实现的功能83.4业务流程图94.详细设计114.1数据库详细设计114.2建立数据库124.3页面详细设计:185用户手册225.1普通用户:225.2管理员:24参考文献3..._计算机科学与技术毕业设计网上书店

素数求和_输入一个正整数n和n个正整数,统计其中素数的和。-程序员宅基地

文章浏览阅读1.6k次。Description输入一个正整数N和N个正整数,统计其中素数的和。Input输入一个正整数N(1≤N≤100)和N个正整数(≥3),用空格分隔。Output输出所有素数,用空格隔开;再输出这些素数和。Sample Input10 4 5 8 12 13 24 34 37 20 885 1 5 8 12 13Sample Output5 13 37 s=555 13 s=..._输入一个正整数n和n个正整数,统计其中素数的和。

Oracle DB 使用RMAN创建备份2_rman 备份 生成两个文件-程序员宅基地

文章浏览阅读2.5k次。归档备份:概念归档备份:概念 如果需要在指定时间内保留联机备份,RMAN 通常会假定用户可能需要在自执行该备份以来到现在之间的任意时间执行时间点恢复。为了满足这一要求,RMAN 会在此时段内保留归档日志。但是,可能仅需要在指定的时间(如两年)内保留特定备份(并使其保持一致和可恢复)。用户不打算恢复到自执行该备份以后的某一时间点,只是希望能够正好恢复到执行该备_rman 备份 生成两个文件