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

智能推荐

幼儿园观察记录的目的和目标_幼儿园游戏观察记录-程序员宅基地

文章浏览阅读3.7k次。幼儿园游戏观察记录角色游戏开始了,大家纷纷选择了自己喜欢的角色开始扮演,你扮演的角色是什么呢,下面是小编整理的幼儿园游戏观察记录,欢迎来参考!幼儿姓名:xx观察地点:教室观察者:顾xx游戏名称:分种子目标1、具有初步的探究能力。2、能对事物或现象进行观察比较,发现其相同与不同。观察要点1、能区分各种不同的种子并进行归类。2、掌握各种子的名称,知道种子的不同用途。观察记录上周我们组织了教学活动《好玩..._幼儿游戏观察目的

System系统类常用方法大全_system类有哪些方法-程序员宅基地

文章浏览阅读811次。如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。加载加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以_system类有哪些方法

Arduino Leonardo 按键控制LED亮与灭-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏7次。按键控制LED亮灭,试用两种方法,一种是最简单的开关,另一种就是用按键的IO来控制LED。

直播app源代码,监听软键盘_安卓键盘监控源码-程序员宅基地

文章浏览阅读102次。直播app源代码,监听软键盘实现的相关代码1。ManiFest 增加android:windowSoftInputMode="adjustResize"2。添加监听,其中rootview是页面根布局rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void o_安卓键盘监控源码

一个完整的网站建设需要哪些流程?_一个完整的网站设计需要-程序员宅基地

文章浏览阅读1.5k次。摘要网站建设流程并不复杂,大致流程就是域名和服务器的购买,网站设计和开发,内容的补充,但在这些环境中,有很多需要注意的小细节。1、针对对象,用户调研在设计网站前,必须告诉网站所针对的人群、区域、国家等;提供网站策划书,这样在设计上就会针对这种人群的浏览习惯特别定制您的网页。网站建设前期准备工作相当重要,这决定您建站的目的,以及日后维护网站,让您的网站发挥作用等是相当的重要。2、内容规划,分析网站功能和需求(网站策划)网站频道就是网站的大框架,也就是主体部分,比如:首页,公司简介,.._一个完整的网站设计需要

【区块链】【以太坊】GHOST协议的浅析-程序员宅基地

文章浏览阅读1.5k次,点赞2次,收藏3次。GHOST全称为Greedy Heaviest-Observed Sub-Tree,即贪婪最重可观察子树协议。与Bitcoin中的最长链原则不同,以太坊使用GHOST协议,通过判断最重子树来决定主链。算法描述选择区块作为主链的算法Input: Block tree T1. set B ← Genesis Block2. if ChildrenT (B) = ∅ then retu...

随便推点

HDU - 1828(矩形周长并 扫描线算法 )-程序员宅基地

文章浏览阅读701次。#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1const int maxn = 31111;int sumseg[maxn<<2],col[maxn<<2],len[maxn<<2],lbd[maxn<<2],rbd[maxn<<2];void build(int l,int r,int rt){sumseg[rt]=len[rt]=

画图板的实现-程序员宅基地

文章浏览阅读169次。[b][size=large][align=center]画图板的实现[/align][/size][/b][size=medium]项目截图:[/size][align=center][img]http://dl.iteye.com/upload/attachment/0075/7120/f4268ecf-258e-32b0-8508-7dc5ac6d1fc8.jpg[/img][/alig...

小爱同学自定义音色_小爱同学 自定义音色-程序员宅基地

文章浏览阅读2.3w次,点赞5次,收藏7次。最近小爱同学可以自定义音色了,但是部分童鞋机型不支持,怎么办呢?别着急,老衲来教你3步轻松解决。但是有个前提,你是小米手机。下载一个低版本的小爱同学APP可以在此处下载:https:/..._小爱同学 自定义音色

RouterOS备份系统到163邮箱-程序员宅基地

文章浏览阅读1.6k次。在System-&gt;Scheduler里,添加一个Scheduler::global mailip:set mailip [:resolve smtp.163.com]:global mailuser “XXX”:global mailpw “XXX”:global Tomail “[email protected]”/tool e-mail set [email protected]...

详细的键盘键值表-程序员宅基地

文章浏览阅读1.2w次。详细的键盘键值表 常数名称十六进制值十进制值对应按键VK_LBUTTON011鼠标的左键VK_RBUTTON022鼠标的右键VK-CANCEL033Ctrl+Break(通常不需要处理)

热修复JSPatch之接口设计_patch接口-程序员宅基地

文章浏览阅读1k次。接上篇文章《iOS紧急发布实践心得》 中所说为了减少紧急发布的次数,同事也为了保证能够从容的解决我们的线上 bug,不造成重大的影响,拉低我们的KPI,我们可以建立属于我们自己的热修复机制。准备:什么是JSpatch?   这个网上已经有很多的介绍了,这里就不详细的说了。总而言之,JSpatch就是gitHub上的一个开源库,它利用了OC中的runtime机制,实现了利用javaScript脚本替换_patch接口