《十八》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.mzph.cn/diannao/7857.shtml

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

相关文章

02-Fortran基础--Fortran操作符与控制结构

02-Fortran基础--Fortran操作符与控制结构 0 引言1 操作符1.1 数学运算符1.2 逻辑运算符1.3 关系运算符 2 控制流程2.1 条件结构2.2 循环结构2.3 分支结构 0 引言 运算符和控制流程对编程语言是必须的,Fortran的操作符和控制流程涉及到各种数学运算符、逻辑运算符以及控制结构。…

一文了解栈

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、栈是什么&#xff1f;二、栈的实现思路1.顺序表实现2.单链表实现3.双向链表实现 三、接口函数的实现1.栈的定义2.栈的初始化3.栈的销毁4.入栈5.出栈6.返回栈…

调用nvprof报错: No kernels were profiled. No API activities were profiled.

调用nvprof报错 1 nvprof介绍 nvprof 是 NVIDIA 提供的一款用于分析 CUDA 应用程序性能的命令行性能分析器。CUDA 是一种并行计算平台和编程模型&#xff0c;允许开发人员利用 NVIDIA GPU 进行通用处理。 nvprof 帮助开发人员分析其 CUDA 应用程序的性能&#xff0c;提供各种…

C++继承 - 下

目录 1. 继承和友元 2. 继承与静态成员 3. 菱形继承以及菱形虚拟继承 3.1. 单继承 3.2. 多继承 3.3. 菱形继承 3.4. 菱形虚拟继承 3.5. 菱形继承的底层细节 3.6. 菱形虚拟继承的底层细节 3.7. 虚拟继承 4. 继承的总结 5. 相关继承练习题 5.1. 如何定义一个无法被继…

【Vue3】Ref与Reactive

3.1【ref 创建&#xff1a;基本类型的响应式数据】 作用&#xff1a;定义响应式变量。语法&#xff1a;let xxx ref(初始值)。返回值&#xff1a;一个RefImpl的实例对象&#xff0c;简称ref对象或ref&#xff0c;ref对象的value属性是响应式的。注意点&#xff1a; JS中操作数…

Windows环境编译 VVenC 源码生成 Visual Studio 工程

VVenC介绍 Fraunhofer通用视频编码器(VVenC)的开发是为了提供一种公开可用的、快速和有效的VVC编码器实现。VVenC软件基于VTM&#xff0c;其优化包括软件重新设计以减轻性能瓶颈、广泛的SIMD优化、改进的编码器搜索算法和基本的多线程支持以利用并行。此外&#xff0c;VVenC支…

加索引导致表被锁的原因及处理方法

目录 为什么加索引会导致表被锁&#xff1f;什么情况下会被锁&#xff1f;要注意什么&#xff1f;被锁怎么处理&#xff1f;MySQL查询被锁的表查询被锁的库 PostgreSQL查询被锁的表查询被锁的库 Oracle查询被锁的表查询被锁的库 SQL Server查询被锁的表查询被锁的库 结语 在数据…

Mybatis plus update PG json 类型 报错解决

Mybatis plus update PG json 类型 报错解决 1. 定义的PG数据库对象2. 自定义 JSON Handler3. update Wrapper4. update 报错信息4.1 No hstore extension installed.4.2 Error setting non null for parameter #1 with JdbcType null . Try setting a different JdbcType for …

git commit 提交报错pre-commit hook failed (add --no-verify to bypass) 解决方法,亲测有效

问题截图 今天在执行 git commit 命令时报错&#xff1a;pre-commit hook failed (add --no-verify to bypass) 解决 参考文章&#xff1a;git commit报错&#xff1a;pre-commit hook failed的解决方法 具体原理什么的就不解释了&#xff0c;可以看看上面的参考文章 解决方…

TXT文本高效批量编辑,支持批量将每个单号间的空白行进行删除掉,文本内容管理更方便

TXT文本是一种常用的存储快递单号的数据格式。然而&#xff0c;当TXT文本中存在大量的空白行时&#xff0c;不仅浪费了存储空间&#xff0c;还可能导致批量编辑和查询变得低效。为了解决这一问题&#xff0c;我们推出了高效的TXT文本批量编辑功能&#xff0c;支持批量删除单号间…

PTQ4SAM、Mamba-Attention、AniTalker、IceFormer、U-DiTs、CogDPM

本文首发于公众号&#xff1a;机器感知 PTQ4SAM、Mamba-Attention、AniTalker、IceFormer、U-DiTs、CogDPM PTQ4SAM: Post-Training Quantization for Segment Anything Segment Anything Model (SAM) has achieved impressive performance in many computer vision tasks. Ho…

Day 24 数据库管理及数据类型

数据库管理及数据类型 一&#xff1a;数据类型 1.数值类型 整数类型 ​ 整数类型&#xff1a;TINYINT SMALLINT MEDIUMINT INT BIGINT ​ 作用&#xff1a;用于存储用户的年龄、游戏的Level、经验值等 浮点数类型 ​ 浮点数类型&#xff1a;FLOAT DOUBLE ​ 作用&#xf…

Docker镜像仓库-在私有镜像仓库推送或拉取镜像

推送镜像到私有仓库&#xff0c;要先让镜像打包 前缀为私有仓库地址的名字&#xff1a; 这里也是打包成功了:docker images 可以查看到 push推送镜像到镜像仓库: docker push 192.168.221.129:8080/nginx:1.0推送成功后在主机访问镜像仓库可以看到 这里已经有个镜像了。而且可…

Lua(0)环境搭建与基础代码

Lua环境搭建 1、下载 lua for window https://github.com/rjpcomputing/luaforwindows Download-->LuaForWindows_v5.1.5-52.exe 安装完成后&#xff0c;cmd--->lua&#xff0c;显示lua版本则成功 2、Sublime Text https://www.sublimetext.com/download 参考课程&a…

CyberDemo解读

CyberDemo: Augmenting Simulated Human Demonstration for Real-World Dexterous Manipulation解读 摘要1. 简介2. Related Work2.1 Data for Learning Robot Manipulation2.2 机器人的预训练视觉表征2.3 Sim2Real Transfer 3. CyberDemo3.1 收集人体遥操作数据3.2 在模拟器中…

工程绘图神器:Origin 2021软件安装与图像demo水印问题解决

目录 引言 正文 01-Origin软件简介 02-Origin软件安装 03-Origin软件复制图像带有水印问题解决 引言 注&#xff1a;本篇软件安装内容引用了微信公众号“软件管家”里的Origin 2021安装教程和…

张大哥笔记:自媒体人10种赚钱方法

很多人都在做自媒体&#xff0c;比如平台广告分成、广告收入、公关宣传、品牌植入、演讲、会员制、出书、线下活动。那么本文介绍了自媒体人10种赚钱方法&#xff0c;供大家参考&#xff1a; 1、打造个人IP 什么是个人IP&#xff1f;在百度百科上是这样解释的&#xff1a;指个…

NVME Doorbell 寄存器 数据请求时doorbell 处理

3.NVMe寄存器配置 3.1 寄存器定义 NVMe寄存器主要分为两部分&#xff0c;一部分定义了Controller整体属性&#xff0c;一部分用来存放每组队列的头尾DB寄存器。 CAP——控制器能力&#xff0c;定义了内存页大小的最大最小值、支持的I/O指令集、DB寄存器步长、等待时间界限、仲…

京东生产环境十万并发秒杀系统三高架构

文章目录 三高——高并发、高可用、高可扩展用数据库乐观锁解决超卖阿里巴巴&#xff1a;为了提升数据库性能&#xff0c;对数据库的源码级别做了改造——在DB内部实现内存队列&#xff0c;一次性接收很多的请求&#xff0c;一次性更新。京东&#xff1a;redis&#xff0c;mq&a…

无线通信基础

这里写目录标题 通信概述什么是无线通信无线通信电磁波 通信概述 什么是无线通信 无线通信 : 是指利用电磁波信号可以在自由空间中传播的特性进行信息交换的一种通信方式 无线通信的关键技术包括调制技术、解调技术、信道编码技术、信号处理技术、天线技术等。这些技术的不断…