《十八》QThread多线程组件

本章将重点介绍如何运用QThread组件实现多线程功能。

多线程技术在程序开发中尤为常用,Qt框架中提供了QThread库来实现多线程功能。当你需要使用QThread时,需包含QThread模块,以下是QThread类的一些主要成员函数和槽函数。

成员函数/槽函数   

描述
QThread(QObject *parent = nullptr)    构造函数,创建一个QThread对象。
~QThread()    析构函数,释放QThread对象。
void start(QThread::Priority priority = InheritPriority)    启动线程。
void run()    默认的线程执行函数,需要在继承QThread的子类中重新实现以定义线程的操作。
void exit(int returnCode = 0)    请求线程退出,线程将在适当的时候退出。
void quit()   请求线程退出,与exit()类似。
void terminate()   立即终止线程的执行。这是一个危险的操作,可能导致资源泄漏和未完成的操作。
void wait()   等待线程完成。主线程将被阻塞,直到该线程退出。
bool isRunning() const    检查线程是否正在运行。
void setPriority(Priority priority)    设置线程的优先级。
Priority priority() const   获取线程的优先级。
QThread::Priority priority()    获取线程的优先级。
void setStackSize(uint stackSize)    设置线程的堆栈大小(以字节为单位)。
uint stackSize() const   获取线程的堆栈大小。
void msleep(unsigned long msecs)    使线程休眠指定的毫秒数。
void sleep(unsigned long secs)    使线程休眠指定的秒数。
static QThread *currentThread()   获取当前正在执行的线程的QThread对象。
void setObjectName(const QString &name)    为线程设置一个对象名。

当我们需要创建线程时,通常第一步则是要继承QThread类,并重写类内的run()方法,在run()方法中,你可以编写需要在新线程中执行的代码。当你创建一个QThread的实例并调用它的start()方法时,会自动调用run()来执行线程逻辑,如下这样一段代码展示了如何运用线程类。

#include <QCoreApplication>
#include <QThread>
#include <QDebug>class MyThread : public QThread
{
public:void run() override{for (int i = 0; i < 5; ++i){qDebug() << "Thread is running" << i;sleep(1);}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);MyThread thread;thread.start();thread.wait();qDebug() << "Main thread is done.";return a.exec();
}

上述代码运行后则会每隔1秒输出一段话,在主函数内通过调用thread.start方法启动这个线程,并通过thread.wait等待线程结束,如下图所示;

1.1 线程组与多线程 

        线程组是一种组织和管理多个线程的机制,允许将相关联的线程集中在一起,便于集中管理、协调和监控。通过线程组,可以对一组线程进行统一的生命周期管理,包括启动、停止、调度和资源分配等操作。

        上述方法并未真正实现多线程功能,我们继续完善MyThread自定义类,在该类内增加两个标志,is_run()用于判断线程是否正在运行,is_finish()则用来判断线程是否已经完成,并在run()中增加打印当前线程对象名称的功能。

class MyThread: public QThread
{
protected:volatile bool m_to_stop;protected:void run(){for(int x=0; !m_to_stop && (x <10); x++){msleep(1000);std::cout << objectName().toStdString() << std::endl;}}public:MyThread(){m_to_stop = false;}void stop(){m_to_stop = true;}void is_run(){std::cout << "Thread Running = " << isRunning() << std::endl;}void is_finish(){std::cout << "Thread Finished = " << isFinished() << std::endl;}};

接着在主函数内调整,增加一个MyThread thread[10]用于存储线程组,线程组是一种用于组织和管理多个线程的概念。在不同的编程框架和操作系统中,线程组可能具有不同的实现和功能,但通常用于提供一种集中管理和协调一组相关线程的机制。

我们通过循环的方式依次对线程组进行赋值,通过调用setObjectName对每一个线程赋予一个不同的名称,当需要使用这些线程时则可以通过循环调用run()方法来实现,而结束调用同样如此,如下是调用的具体实现;

#include <QCoreApplication>
#include <iostream>
#include <QThread>int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 定义线程数组MyThread thread[10];// 设置线程对象名字for(int x=0;x<10;x++){thread[x].setObjectName(QString("thread => %1").arg(x));}// 批量调用run执行for(int x=0;x<10;x++){thread[x].start();thread[x].is_run();thread[x].isFinished();}// 批量调用stop关闭for(int x=0;x<10;x++){thread[x].wait();thread[x].stop();thread[x].is_run();thread[x].is_finish();}return a.exec();
}

如下图则是运行后实现的多线程效果;

1.2 向线程中传递参数 

向线程中传递参数是多线程编程中常见的需求,不同的编程语言和框架提供了多种方式来实现这个目标,在Qt中,由于使用的自定义线程类,所以可通过增加一个set_value()方法来向线程内传递参数,由于线程函数内的变量使用了protected属性,所以也就实现了线程间变量的隔离,当线程被执行结束后则可以通过result()方法获取到线程执行结果,这个线程函数如下所示;

class MyThread: public QThread
{
protected:int m_begin;int m_end;int m_result;void run(){m_result = m_begin + m_end;}public:MyThread(){m_begin = 0;m_end = 0;m_result = 0;}// 设置参数给当前线程void set_value(int x,int y){m_begin = x;m_end = y;}// 获取当前线程名void get_object_name(){std::cout << "this thread name => " << objectName().toStdString() << std::endl;}// 获取线程返回结果int result(){return m_result;}
};

在主函数中,我们通过MyThread thread[3];来定义3个线程组,并通过循环三次分别thread[x].set_value()设置三组不同的参数,当设置完成后则可以调用thread[x].start()方法运行这些线程,线程运行结束后则返回值将会被依次保存在thread[x].result()中,此时直接将其相加即可得到最终线程执行结果;

#include <QCoreApplication>
#include <iostream>
#include <QThread>int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);MyThread thread[3];// 分别将不同的参数传入到线程函数内for(int x=0; x<3; x++){thread[x].set_value(1,2);thread[x].setObjectName(QString("thread -> %1").arg(x));thread[x].start();}// 等待所有线程执行结束for(int x=0; x<3; x++){thread[x].get_object_name();thread[x].wait();}// 获取线程返回值并相加int result = thread[0].result() + thread[1].result() + thread[2].result();std::cout << "sum => " << result << std::endl;return a.exec();
}

 程序运行后,则可以输出三个线程相加的和;

1.3 互斥同步线程锁

QMutex 是Qt框架中提供的用于线程同步的类,用于实现互斥访问共享资源。Mutex是“互斥锁(Mutual Exclusion)”的缩写,它能够确保在任意时刻,只有一个线程可以访问被保护的资源,从而避免了多线程环境下的数据竞争和不一致性。

在Qt中,QMutex提供了简单而有效的线程同步机制,其基本用法包括:

  •     锁定(Lock): 线程在访问共享资源之前,首先需要获取QMutex的锁,这通过调用lock()方法来实现。
  •     解锁(Unlock): 当线程使用完共享资源后,需要释放QMutex的锁,以允许其他线程访问,这通过调用unlock()方法来实现。

该锁lock()锁定与unlock()解锁必须配对使用,线程锁保证线程间的互斥,利用线程锁能够保证临界资源的安全性。

  • 线程锁解决的问题: 多个线程同时操作同一个全局变量,为了防止资源的无序覆盖现象,从而需要增加锁,来实现多线程抢占资源时可以有序执行。
  • 临界资源(Critical Resource): 每次只允许一个线程进行访问 (读/写)的资源。
  • 线程间的互斥(竞争): 多个线程在同一时刻都需要访问临界资源。
  • 一般性原则: 每一个临界资源都需要一个线程锁进行保护。

我们以生产者消费者模型为例来演示锁的使用方法,生产者消费者模型是一种并发编程中常见的同步机制,用于解决多线程环境下的协作问题。该模型基于两类角色:生产者(Producer)和消费者(Consumer),它们通过共享的缓冲区进行协作。

生产者消费者模型的典型应用场景包括异步任务处理、事件驱动系统、数据缓存等。这种模型的实现可以通过多线程编程或使用消息队列等方式来完成。

首先在全局中引入#include <QMutex>库,并在全局定义static QMutex线程锁变量,接着我们分别定义两个自定义线程函数,其中Producer代表生产者,而Customer则是消费者,生产者中负责每次产出一个随机数并将其追加到g_store全局变量内保存,消费者则通过g_store.remove每次取出一个元素。

static QMutex g_mutex;      // 线程锁
static QString g_store;     // 定义全局变量class Producer : public QThread
{
protected:void run(){int count = 0;while(true){// 加锁g_mutex.lock();g_store.append(QString::number((count++) % 10));std::cout << "Producer -> "<< g_store.toStdString() << std::endl;// 释放锁g_mutex.unlock();msleep(900);}}
};class Customer : public QThread
{
protected:void run(){while( true ){g_mutex.lock();if( g_store != "" ){g_store.remove(0, 1);std::cout << "Curstomer -> "<< g_store.toStdString() << std::endl;}g_mutex.unlock();msleep(1000);}}
};

在主函数中分别定义两个线程类,并依次运行它们;

int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Producer p;Customer c;p.setObjectName("producer");c.setObjectName("curstomer");p.start();c.start();return a.exec();
}

至此,生产者产生数据,消费者消费数据;如下图所示;

QMutexLocker 是Qt框架中提供的一个辅助类,它是在QMutex基础上简化版的线程锁,QMutexLocker会保护加锁区域,并自动实现互斥量的锁定和解锁操作,可以将其理解为是智能版的QMutex锁,通过 QMutexLocker可以确保在作用域内始终持有锁,从而避免因为忘记释放锁而导致的问题。该锁只需要在上方代码中稍加修改即可。

使用 QMutexLocker 的一般流程如下:

  1. 创建一个 QMutex 对象。
  2. 创建一个 QMutexLocker 对象,传入需要锁定的 QMutex
  3. QMutexLocker 对象的作用域内进行需要互斥访问的操作。
  4. QMutexLocker 对象超出作用域范围时,会自动释放锁。
static QMutex g_mutex;      // 线程锁
static QString g_store;     // 定义全局变量class Producer : public QThread
{
protected:void run(){int count = 0;while(true){// 增加智能线程锁QMutexLocker Locker(&g_mutex);g_store.append(QString::number((count++) % 10));std::cout << "Producer -> "<< g_store.toStdString() << std::endl;msleep(900);}}
};

 1.4 读写同步线程锁

QReadWriteLock 是Qt框架中提供的用于实现读写锁的类。读写锁允许多个线程同时读取共享数据,但在写入数据时会互斥,确保数据的一致性和完整性。这对于大多数情况下读取频繁而写入较少的共享数据非常有用,可以提高程序的性能。

其提供了两种锁定操作:

  • 读取锁(Read Lock): 允许多个线程同时获取读取锁,用于并行读取共享数据。在没有写入锁的情况下,多个线程可以同时持有读取锁。
  • 写入锁(Write Lock): 写入锁是互斥的,当一个线程获取写入锁时,其他线程无法获取读取锁或写入锁。这确保了在写入数据时,不会有其他线程同时读取或写入。

互斥锁存在一个问题,每次只能有一个线程获得互斥量的权限,如果在程序中有多个线程来同时读取某个变量,那么使用互斥量必须排队,效率上会大打折扣,基于QReadWriteLock读写模式进行代码段锁定,即可解决互斥锁存在的问题。

#include <QCoreApplication>
#include <iostream>
#include <QThread>
#include <QMutex>
#include <QReadWriteLock>static QReadWriteLock g_mutex;      // 线程锁
static QString g_store;             // 定义全局变量class Producer : public QThread
{
protected:void run(){int count = 0;while(true){// 以写入方式锁定资源g_mutex.lockForWrite();g_store.append(QString::number((count++) % 10));// 写入后解锁资源g_mutex.unlock();msleep(900);}}
};class Customer : public QThread
{
protected:void run(){while( true ){// 以读取方式写入资源g_mutex.lockForRead();if( g_store != "" ){std::cout << "Curstomer -> "<< g_store.toStdString() << std::endl;}// 读取到后解锁资源g_mutex.unlock();msleep(1000);}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Producer p1,p2;Customer c1,c2;p1.setObjectName("producer 1");p2.setObjectName("producer 2");c1.setObjectName("curstomer 1");c2.setObjectName("curstomer 2");p1.start();p2.start();c1.start();c2.start();return a.exec();
}

 该锁允许用户以同步读lockForRead()或同步写lockForWrite()两种方式实现保护资源,但只要有一个线程在以写的方式操作资源,其他线程也会等待写入操作结束后才可继续读资源。

1.5 基于信号线程锁

QSemaphore 是Qt框架中提供的用于实现信号量的类。信号量是一种用于在线程之间进行同步和通信的机制,它允许多个线程在某个共享资源上进行协调,控制对该资源的访问。QSemaphore 的主要作用是维护一个计数器,线程可以通过获取和释放信号量来改变计数器的值。

其主要方法包括:

  • QSemaphore(int n = 0):构造函数,创建一个初始计数值为 n 的信号量。
  • void acquire(int n = 1):获取信号量,将计数器减去 n。如果计数器不足,线程将阻塞等待。
  • bool tryAcquire(int n = 1):尝试获取信号量,如果计数器足够,立即获取并返回 true;否则返回 false。
  • void release(int n = 1):释放信号量,将计数器加上 n。如果有等待的线程,其中一个将被唤醒。

信号量是特殊的线程锁,信号量允许N个线程同时访问临界资源,通过acquire()获取到指定资源,release()释放指定资源。

#include <QCoreApplication>
#include <iostream>
#include <QThread>
#include <QSemaphore>const int SIZE = 5;
unsigned char g_buff[SIZE] = {0};QSemaphore g_sem_free(SIZE); // 5个可生产资源
QSemaphore g_sem_used(0);    // 0个可消费资源// 生产者生产产品
class Producer : public QThread
{
protected:void run(){while( true ){int value = qrand() % 256;// 若无法获得可生产资源,阻塞在这里g_sem_free.acquire();for(int i=0; i<SIZE; i++){if( !g_buff[i] ){g_buff[i] = value;std::cout << objectName().toStdString() << " --> " << value << std::endl;break;}}// 可消费资源数+1g_sem_used.release();sleep(2);}}
};// 消费者消费产品
class Customer : public QThread
{
protected:void run(){while( true ){// 若无法获得可消费资源,阻塞在这里g_sem_used.acquire();for(int i=0; i<SIZE; i++){if( g_buff[i] ){int value = g_buff[i];g_buff[i] = 0;std::cout << objectName().toStdString() << " --> " << value << std::endl;break;}}// 可生产资源数+1g_sem_free.release();sleep(1);}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Producer p1;Customer c1;p1.setObjectName("producer");c1.setObjectName("curstomer");p1.start();c1.start();return a.exec();
}

 本篇原作者链接:C++ Qt开发:运用QThread多线程组件_c++ qt qthread-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/321421.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

本地部署大模型ollama+docker+open WebUI/Lobe Chat

文章目录 大模型工具Ollama下载安装运行Spring Ai 代码测试加依赖配置写代码 ollama的web&Desktop搭建部署Open WebUI有两种方式Docker DesktopDocker部署Open WebUIDocker部署Lobe Chat可以配置OpenAI的key也可以配置ollama 大模型的选择 本篇基于windows环境下配置 大模型…

HarmonyOS实战开发教程-如何开发一个2048游戏

今天为大家分享的是2048小游戏&#xff0c;先看效果图&#xff1a; 这个项目对于新手友友来说可能有一点难度&#xff0c;但是只要坚持看完一定会有收获。因为小编想分享的并不局限于ArkTs语言&#xff0c;而是编程思想。 这个游戏的基本逻辑是初始化一个4乘4的数组&#xff…

论文笔记ColdDTA:利用数据增强和基于注意力的特征融合进行药物靶标结合亲和力预测

ColdDTA发表在Computers in Biology and Medicine 的一篇一区文章 突出 • 数据增强和基于注意力的特征融合用于药物靶点结合亲和力预测。 • 与其他方法相比&#xff0c;它在 Davis、KIBA 和 BindingDB 数据集上显示出竞争性能。 • 可视化模型权重可以获得可解释的见解。 …

并发编程之阻塞队列BlockingQueue实战及其原理分析

1. 阻塞队列介绍 1.1 队列 是限定在一端进行插入&#xff0c;另一端进行删除的特殊线性表。 先进先出(FIFO)线性表。 允许出队的一端称为队头&#xff0c;允许入队的一端称为队尾。

轻松应对数据恢复挑战:雷神笔记本,不同情况不同策略

在数字化时代&#xff0c;数据无疑是我们生活中不可或缺的一部分。无论是重要的工作文件、珍贵的家庭照片&#xff0c;还是回忆满满的视频&#xff0c;一旦丢失&#xff0c;都可能给我们的生活带来诸多不便。雷神笔记本作为市场上备受欢迎的电脑品牌&#xff0c;用户在使用过程…

【JS篇之】异常

前言&#xff1a;在代码编写过程中&#xff0c;最常遇到的就是程序异常。其实异常并非坏事&#xff0c;它可以让开发人员及时发现、定位到错误&#xff0c;提醒我们做正确的事情&#xff0c;甚至在某些时候&#xff0c;我们还会手动抛出异常。 1.异常的分类 在JS中&#xff0…

ABB RobotStudio学习记录(一)新建工作站

RobotStudio新建工作站 最近遇到 虚拟示教器和 Rapid 代码不能控制 视图中机械臂的问题&#xff0c;其实是由于机械臂和工作站不匹配。以下是解决方法。 名称版本Robot Studio6.08 新建一个”空工作站“&#xff1b; 在目标位置新建一个目标文件夹 C:\solution\test&#xff0…

数据交换和异步请求(JSONAjax))

目录 一.JSON介绍1.JSON的特点2.JSON的结构3.JSON的值JSON示例4.JSON与字符串对象转换5.注意事项 二.JSON在Java中的使用1.Javabean to json2.List to json3.Map to JSONTypeToken底层解析 三.Ajax介绍1.介绍2.Ajax经典应用场景 四.Ajax原理示意图1. 传统web应用2.Ajax方法 五.…

平面模型上提取凸凹多边形------pcl

平面模型上提取凸凹多边形 pcl::PointCloud<pcl::PointXYZ>::Ptr PclTool::ExtractConvexConcavePolygons(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) {pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);p…

java10基础(this super关键字 重写 final关键字 多态 抽象类)

目录 一. this和super关键字 1. this关键字 2. super关键字 二. 重写 三. final关键字 四. 多态 五. 抽象类 1. 抽象方法 2. 抽象类 3. 面向抽象设计 一. this和super关键字 1. this关键字 this 当前对象的引用 this.属性 this.方法名() this() -- 调用构造函数 …

Vue阶段练习:初始化渲染、获取焦点、记账清单

阶段练习主要承接Vue 生命周期-CSDN博客 &#xff0c;学习完该部分内容后&#xff0c;进行自我检测&#xff0c;每个练习主要分为效果显示、需求分析、静态代码、完整代码、总结 四个部分&#xff0c;效果显示和准备代码已给出&#xff0c;我们需要完成“完整代码”部分。 练习…

技术速递|使用 .NET 为 Microsoft AI 构建可扩展网关

作者&#xff1a;Kara Saucerman 排版&#xff1a;Alan Wang Microsoft AI 团队构建了全面的内容、服务、平台和技术&#xff0c;以便消费者在任何设备上、任何地方获取他们想要的信息&#xff0c;并为企业改善客户和员工的体验。我们的团队支持多种体验&#xff0c;包括 Bing、…

Java 线程池 ( Thread Pool )的简单介绍

想象一下&#xff0c;你正指挥着一支超级英雄团队&#xff0c;面对蜂拥而至的敌人&#xff08;任务&#xff09;&#xff0c;不是每次都召唤新英雄&#xff08;创建线程&#xff09;&#xff0c;而是精心调配现有成员&#xff0c;高效应对。这就是Java线程池的魔力&#xff0c;…

毕业就业信息|基于Springboot+vue的毕业就业信息管理系统的设计与实现(源码+数据库+文档)

毕业就业信息管理系统 目录 基于Springboot&#xff0b;vue的毕业就业信息管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1学生信息管理 2 公司信息管理 3公告类型管理 4公告信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设…

探索GitHub上的GPTs项目:泄露和被破解的GPT提示

GPTs项目是一个在GitHub上由用户linexjlin发起的开源项目&#xff0c;专注于提供泄露的GPT&#xff08;生成式预训练转换器&#xff09;提示。这些提示用于指导和优化AI模型的输出&#xff0c;进而提升代码生成的质量和效率。项目页面提供了丰富的功能和资源&#xff0c;旨在帮…

3D渲染是什么?渲染100邀请码1a12

3D渲染是把3D模型转换为2D图像或动画的过程&#xff0c;涉及到多方面知识&#xff0c;这篇文章我们就来了解下。 1、3D渲染的原理 3D渲染的原理是模拟光线在三维空间中的传播和反射&#xff0c;根据物体在空间中的分布&#xff0c;计算出每个像素的颜色和亮度等数值&#xff…

如果insightface/instantID安装失败怎么办(关于InsightFaceLoader_Zho节点的报错)

可能性有很多&#xff0c;但是今天帮朋友解决问题的时候又收集了一种新的思路。 首先&#xff0c;可以先按照这篇文章里边提到的方法去安装&#xff1a; 【全网最详细】ComfyUI下&#xff0c;Insightface安装指南-聚梦小课堂_insightface如何安装-CSDN博客 其次&#xff0c;…

在Java中如何有效地处理内存泄露

在Java中&#xff0c;处理内存泄露有多种方法&#xff0c;以下是其中三种常见的方法及其原理和适用场景&#xff1a; ## 1. 合理使用垃圾回收机制 Java中的垃圾回收机制&#xff08;Garbage Collection&#xff0c;GC&#xff09;是一种自动化的内存管理技术&#xff0c;它可以…

2005-2021年全国各地级市生态环境注意力/环保注意力数据(根据政府报告文本词频统计)

2005-2021年全国各地级市生态环境注意力/环保注意力数据&#xff08;根据政府报告文本词频统计&#xff09; 2005-2021年全国各地级市生态环境注意力/环保注意力数据&#xff08;根据政府报告文本词频统计&#xff09; 1、时间&#xff1a;2005-2021年 2、范围&#xff1a;2…

嵌入式开发常见概念简介

目录 0. 《STM32单片机自学教程》专栏总纲 API Handle(句柄) 0. 《STM32单片机自学教程》专栏总纲 本文作为专栏《STM32单片机自学教程》专栏其中的一部分&#xff0c;返回专栏总纲&#xff0c;阅读所有文章,点击Link: STM32单片机自学教程-[目录总纲]_stm32 学习-CSD…