【Qt基本功修炼】Qt线程的两种运行模式

2024-02-03 05:28

本文主要是介绍【Qt基本功修炼】Qt线程的两种运行模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 前言

QThread是Qt中的线程类,用于实现多线程运行。

QThread有两种工作模式,即

  • 消息循环模式
  • 无消息循环模式

两种模式分别适用于不同的场景。下面我们将从多个方面,讲解QThread两种工作模式的区别。

2. 消息循环模式

2.1 实现原理

QThread::run中的代码是在子线程中运行的。

QThread::run是虚函数,从它的默认实现的中可以看到,在QThread::run中启动了一个QEventLoop,即事件循环。部分源码如下所示(文件路径Qt5.9.9\5.9.9\Src\qtbase\src\corelib\thread\qthread.cpp):

QThread::run()
其中,

QThread::exec()

有了事件循环,子线程就可以像UI线程(即主线程)一样,进行消息处理。

2.2 使用方法

设worker是QObject子类的一个对象,具有信号和槽。如何让worker的槽函数运行于子线程中?可以使用 QObject::moveToThread()函数来将worker对象关联到某个子线程中。典型的写法如下:

QThread *thread = new QThread(this); // 线程对象
Worker *worker = new Worker(this); // 工人对象
worker->moveToThread(thread); // worker-thread 关联
thread->start(); // 启动线程

此后,通过Qt::AutoConnection/Qt::QueuedConnection/Qt::BlockQueuedConnection方式连接到worker的信号一旦被触发,Qt内部就会向worker所在子线程插入一条消息,等到消息循环处理到这条消息时,worker的槽函数会在子线程中被执行。

同理,若worker的某个信号通过Qt::AutoConnection/Qt::QueuedConnection/Qt::BlockQueuedConnection方式连接到主线程的一个对象的槽函数上,当此信号触发时,Qt也会向主线程的消息队列中插入一条消息。当主线程的消息循环处理到这条消息时,主线程对象的槽函数会在主线程中被执行。

简而言之,消息循环模式下,线程之间进行信号槽通信,通常都是由消息机制实现的,并且槽函数是在对象的关联线程中执行的。

2.3 线程退出方法

在消息循环模式下,通过调用QThread::quit() 或 QThread::exit() 方法,可以在对应线程的消息队列中插入一条退出消息,等到此条退出消息被处理时,QEventLoop::exec()返回,即消息循环退出。随后,QThread::run()函数也会退出,线程停止,线程资源被释放。相关Qt源码如下:

QThread::quit()

QThread::exit()

通过停止消息循环来停止线程,是一个异步操作。因为消息循环可能忙于执行某个函数,而无法处理退出消息,这就导致线程的退出时间不确定。所以,QThread::quit()通常和QThread::wait() 在主线程中搭配调用,以实现主线程等待子线程退出。等待时长可以由用户根据实际情况来设置。如果线程退出时间过长,则需要对耗时长的函数进行优化,以实现快速退出的效果。当然如果消息循环空闲,则退出消息会很快得到处理,消息循环和线程会很快退出。

3. 无消息循环模式

3.1 实现原理

用户继承 QThread并重载 QThread::run(),即可使默认的消息循环失效,用户将需要在子线程执行的代码全部写在重载的run()函数内。在这种模式下,只有重载的 run() 函数是在子线程中运行的。弄清楚这一点,才能准确判断一段代码的执行线程。

3.2 使用方法

示例代码如下:

class MyThread : public QThread
{
protected:virtual void run(){// process code here}
}

创建和启动线程:

 MyThread *thread = new MyThread(this);thread->start();

3.3 线程退出方法

因为没有消息循环,所以通过抛退出消息来使线程退出的QThread::quit()函数会失效。有两种退出/停止线程的方法:强制退出和正常退出。

3.3.1 强制退出

使用 QThread::terminate() 函数,可以让操作系统强制停止线程的执行。 调用 QThread::terminate() 后,线程不一定会立即退出,退出时间取决于操作系统。主线程可以调用 QThread::wait() 来等待线程退出。

正如 Qt 文档中所说,强制退出线程会导致资源来不及释放和清理,这会导致软件不稳定,非必要不使用。

3.3.2 正常退出

如果想要让线程正常退出,那么就需要在 QThread::run() 函数中插桩检测停止标记。一旦停止标记为真,则停止当前的工作,做好现场清理和资源释放,然后令 QThread::run() 函数返回即可。同时对外提供停止接口,用于设置停止标记。

示例代码如下:

class MyThread : public QThread
{
protected:virtual void run(){do{// 插桩检测停止标记if (m_stop_flag)break;// process code here// 插桩检测停止标记if (m_stop_flag)break;// process code here// 插桩检测停止标记if (m_stop_flag)break;// process code here} while (0);// 清理现场和资源clean();}// 对外提供停止接口void stop(){m_stop_flag = true;}private:bool m_stop_flag = false;
}

调用stop()接口以后,线程不会立即退出,需要调用 QThread::wait() 来等待线程退出。通过控制插桩位置和频率,可以控制线程退出的速度。停止代码如下:

thread->stop(); // 停止线程
thread->wait(2000); // 等待线程退出,不超过2s

4. 结语

在项目中,我们需要根据实际需求选择正确的线程运行模式,合理地实现软件功能,同时提高导致软件的稳定性和可靠性。

以上是Qt线程的基本使用方法,可以满足基本的使用需求。但用起来还是稍显麻烦。在此基础上,Qt Concurrent模块提供了启动线程的其他简便方式及高级用法,但万变不离其宗,打牢基础以后,学习高级用法会非常简单。对于Qt Concurrent模块,我们将在后面的文章进行讲解。

这篇关于【Qt基本功修炼】Qt线程的两种运行模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Spring Boot3虚拟线程的使用步骤详解

《SpringBoot3虚拟线程的使用步骤详解》虚拟线程是Java19中引入的一个新特性,旨在通过简化线程管理来提升应用程序的并发性能,:本文主要介绍SpringBoot3虚拟线程的使用步骤,... 目录问题根源分析解决方案验证验证实验实验1:未启用keep-alive实验2:启用keep-alive扩展建

Docker镜像pull失败两种解决办法小结

《Docker镜像pull失败两种解决办法小结》有时候我们在拉取Docker镜像的过程中会遇到一些问题,:本文主要介绍Docker镜像pull失败两种解决办法的相关资料,文中通过代码介绍的非常详细... 目录docker 镜像 pull 失败解决办法1DrQwWCocker 镜像 pull 失败解决方法2总

在C#中调用Python代码的两种实现方式

《在C#中调用Python代码的两种实现方式》:本文主要介绍在C#中调用Python代码的两种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#调用python代码的方式1. 使用 Python.NET2. 使用外部进程调用 Python 脚本总结C#调

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

IDEA中Git版本回退的两种实现方案

《IDEA中Git版本回退的两种实现方案》作为开发者,代码版本回退是日常高频操作,IntelliJIDEA集成了强大的Git工具链,但面对reset和revert两种核心回退方案,许多开发者仍存在选择... 目录一、版本回退前置知识二、Reset方案:整体改写历史1、IDEA图形化操作(推荐)1.1、查看提

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

Java终止正在运行的线程的三种方法

《Java终止正在运行的线程的三种方法》停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作,停止一个线程可以用Thread.stop()方法,但最好不要用它,本文给大家介绍了... 目录前言1. 停止不了的线程2. 判断线程是否停止状态3. 能停止的线程–异常法4. 在沉睡中停止5

Redis解决缓存击穿问题的两种方法

《Redis解决缓存击穿问题的两种方法》缓存击穿问题也叫热点Key问题,就是⼀个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,本文给大家介绍了Re... 目录引言解决办法互斥锁(强一致,性能差)逻辑过期(高可用,性能优)设计逻辑过期时间引言缓存击穿:给

Qt 中 isHidden 和 isVisible 的区别与使用小结

《Qt中isHidden和isVisible的区别与使用小结》Qt中的isHidden()和isVisible()方法都用于查询组件显示或隐藏状态,然而,它们有很大的区别,了解它们对于正确操... 目录1. 基础概念2. 区别清见3. 实际案例4. 注意事项5. 总结1. 基础概念Qt 中的 isHidd