QT 线程 QThread QT5.12.3环境 C++实现

一、线程

   QT主线程称为GUI线程,负责初始化界面并监听事件循环,并根据事件处理做出界面上的反馈。如果把一些比较复杂或者费时的操作放在主线程中,界面就会出现卡顿或者无响应的现象。一般主线程负责影响界面上的操作, 子线程负责负责费时的数据处理。


二、使用多线程有什么好处

1. 提高应用界面的响应速度。

        这对于开发图形界面程序尤其重要,当一个操作耗时很长时(比如大批量I/O或大量矩阵变换等CPU密集操作),整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等操作,而使用多线程技术可将耗时长的操作置于一个新的线程,从而不会影响到主GUI线程,从而避免上述问题。

2. 使多核心CPU系统更加有效。

        当线程数不大于CPU核数时,操作系统可以调度不同的线程运行于不同的CPU核上。

3. 改善程序结构。

        一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于程序的理解和维护。

界面操作只能由主线程来操作,其他线程负责处理复杂数据.


三、QThread类常用的方法

Public Functions
	QThread(QObject *parent = 0) //构造函数  //pthread_create
	bool isFinished() const  //判断线程是否退出
	bool	wait(unsigned long time = ULONG_MAX)   //pthread_join(&id)
	//等待某个线程结束,最多等待time ms,如果时间没有设置,那么永远等待。

Public Slots
	void	start(Priority priority = InheritPriority)  //启动线程必须使用start
	void	terminate()-->杀死线程  //pthread_cancel

Static Public Members
	Qt::HANDLE	currentThreadId() [static] //得到当前执行者线程ID,可以直接qDebug
	void	sleep(unsigned long secs) [static]
	void	msleep(unsigned long msecs) [static]
	void	usleep(unsigned long usecs) [static]

	睡眠函数不能在主线程调用,会造成界面卡死。

Protected Functions	
	virtual void run();  //启动新线程不能直接调用run,需要调用start接口,
						//start会启动新线程,然后执行run里的代码块。

四、重写run方法创建线程步骤

1、自定义类继承于QThread。

2、重写 run 函数,run函数内有一个 while 或 for 的循环:执行耗时操作。

3、在主线程中,创建自定类类型的对象,调用start方法启动线程, run方法就是新线程的入口函数。

4、主线程通过调用子线程的公有方法传递参数给子线程。

5、子线程通过信号与槽传递参数给主线程。

6、通过quit+wait方法,通知线程退出,回收线程资源。但wait是一个阻塞方法,如果线程没有退出,wait阻塞等待。一般子线程会设置一个标记为来控制循环的退出,并提供一个公有方法设置标记值。当通知线程退出时,在主线程中调用设置标志的公有方法,将循环条件设为假,线程退出,这时wait回收子线程资源不会造成阻塞。

7、关闭窗口时,要对其他子线程进行管理. 关闭窗口时,窗口会发出destroyed信号,可以通过连接该信号来管理子线程。在QT6中,可能不执行这个信号的槽方法,可以通过重现closeEvent事件来处理。


案例需求:在子线程中, 1秒中输出1个整型的数字, 将数字传到主线程, 在主线程的Label中显示。


代码:

mtthread.h

#ifndef MTTHREAD_H
#define MTTHREAD_H

#include <QObject>
#include <QThread>
#include <QDebug>
class MtThread : public QThread
{
    Q_OBJECT
public:
    explicit MtThread(QObject *parent = nullptr);
    void run();
    void set_flag(int flag);
signals:
    void my_signal(int n);
public slots:

private:
    int flag = 1;
};

#endif // MTTHREAD_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "mtthread.h"
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
private slots:
    void on_pushButton_clicked();

    void on_pushButton_2_clicked();

    void my_signal_slot(int n);

private:
    Ui::Widget *ui;
    MtThread *thread;
};

#endif // WIDGET_H

mtthread.cpp

#include "mtthread.h"

MtThread::MtThread(QObject *parent) : QThread(parent)
{

}

void MtThread::run()
{
    static int n = 0;
    qDebug()<<"current thread:"<<QThread::currentThread();
    while(flag){
        qDebug()<<"n = "<<n++;
        emit my_signal(n); // 发送信号
        sleep(1);
    }
}

void MtThread::set_flag(int flag)
{
    this->flag = flag;
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    thread = new MtThread(this);
    qDebug()<<"main thread:"<<QThread::currentThread();

    connect(this,&Widget::destroyed,this,&Widget::on_pushButton_2_clicked); // 直接点击 X 也能销毁线程
    connect(thread,&MtThread::my_signal,this,&Widget::my_signal_slot);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    thread->start(); // 启动线程
}

void Widget::on_pushButton_2_clicked()
{
    thread->quit(); // (通知)线程退出 (如果任务没做完不会结束!)
    // 线程结束的条件 --> run()运行结束 --> 循环条件为假
    thread->set_flag(0);
    thread->wait(); // 回收线程资源,如果线程没有退出,wait()是一个阻塞函数
    this->close();
}

void Widget::my_signal_slot(int n)
{
    ui->label->setText(QString::number(n));
}

输出:


五、moveToThread方法创建线程步骤

1自定义类继承于QObject, 实现线程的入口函数,方法名自定义。

2主线程中实例化自定义类对象和QThread类型的对象。

3将自定义对象移动到QThread线程中。

4、启动线程, 必须通过信号与槽,让自定义类类型对象调用相应的方法,这个方法就是放到线程中执行(注意:启动线程调用start方法,在这个方法中会发送started信号)。

5、主线程通过调用子线程的公有方法传递参数给子线程。

6、子线程通过信号与槽传递参数给主线程。

7、通过quit+wait方法,通知线程退出,回收线程资源。但wait是一个阻塞方法,如果线程没有退出,wait阻塞等待。一般子线程会设置一个标记为来控制循环的退出,并提供一个公有方法设置标记值。当通知线程退出时,在主线程中调用设置标志的公有方法,将循环条件设为假,线程退出,这时wait回收子线程资源不会造成阻塞。

8、关闭窗口时,要对其他子线程进行管理. 关闭窗口时,窗口会发出destroyed信号,可以通过连接该信号来管理子线程。在QT6中,可能不执行这个信号的槽方法,可以通过重现closeEvent事件来处理。


案例需求:在子线程中, 1秒中输出1个整型的数字, 将数字传到主线程, 在主线程的Label中显示。


代码:

mytask.h

#ifndef MYTASK_H
#define MYTASK_H

#include <QObject>
#include <QDebug>
#include <QThread>
class MyTask : public QObject
{
    Q_OBJECT
public:
    explicit MyTask(QObject *parent = nullptr);
    void thread_task();
    void set_flag(int flag);
signals:
    void my_signal(int n);
public slots:

private:
    int flag = 1;
};

#endif // MYTASK_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QDebug>
#include <QThread>
#include "mytask.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;
    QThread *thread;
    MyTask *task;
};

#endif // WIDGET_H

mytask.cpp

#include "mytask.h"

MyTask::MyTask(QObject *parent) : QObject(parent)
{

}

void MyTask::thread_task()
{
    static int n = 0;
    while(flag){
        qDebug()<<"n = "<<n++;
        emit my_signal(n);
        QThread::sleep(1);
    }
}

void MyTask::set_flag(int flag)
{
    this->flag = flag;
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    thread = new QThread(this);
    qDebug()<<"main thread:"<<QThread::currentThread();

    task = new MyTask; // 不能指定父对象

    task->moveToThread(thread); // 把task移动到thread线程中
    connect(thread,&QThread::started,task,&MyTask::thread_task); // 不能直接调用MyTask里的函数,只能通过槽连接
     connect(this,&Widget::destroyed,this,&Widget::on_pushButton_2_clicked); // 直接点击 X 也能销毁线程
     connect(task,&MyTask::my_signal,this,[=](int n){ // 只能在主线程对界面进行操作
         this->ui->label->setText(QString::number(n));
     });
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    thread->start(); // 启动线程,发送start()信号
}

void Widget::on_pushButton_2_clicked()
{
    thread->quit();
    task->set_flag(0);
    thread->wait();
}

输出:


案例需求:通过线程,实现电子时钟。


代码:

mytime.h

#ifndef MYTIME_H
#define MYTIME_H
 
#include <QObject>
#include <QDebug>
#include <QThread>
#include <QDateTime>
class MyTime : public QObject
{
    Q_OBJECT
public:
    explicit MyTime(QObject *parent = nullptr);
    void thread_time();
    void set_flag(int flag);
signals:
    void my_signal(QString datatime);
public slots:
 
private:
    int flag = 1;
};
 
#endif // MYTIME_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QDebug>
#include <QThread>
#include "mytime.h"
 
namespace Ui {
class Widget;
}
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
 
private slots:
    void on_pushButton_clicked();
 
    void on_pushButton_2_clicked();
 
private:
    Ui::Widget *ui;
    QThread *thread;
    MyTime *time;
};
 
#endif // WIDGET_H

mytime.cpp

#include "mytime.h"
 
MyTime::MyTime(QObject *parent) : QObject(parent)
{
 
}
 
void MyTime::thread_time()
{
    while(flag){
        QDateTime currentTime = QDateTime::currentDateTime();
        QString formattedTime = currentTime.toString("hh:mm:ss");
        qDebug() << formattedTime;
        emit my_signal(formattedTime);
        QThread::sleep(1);
    }
}
 
void MyTime::set_flag(int flag)
{
    this->flag = flag;
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"
 
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    thread = new QThread(this);
    time = new MyTime; // 不能指定父对象
    ui->lcdNumber->setDigitCount(8);
    time->moveToThread(thread); // 移动到thread线程中
    connect(thread,&QThread::started,time,&MyTime::thread_time);
    connect(this,&Widget::destroyed,this,&Widget::on_pushButton_2_clicked); // 直接点击 X 也能销毁线程
    connect(time,&MyTime::my_signal,this,[=](QString datatime){ // 只能在主线程对界面进行操作
        this->ui->lcdNumber->display(datatime);
    });
}
Widget::~Widget()
{
    delete ui;
}
 
void Widget::on_pushButton_clicked()
{
    thread->start();
}
 
void Widget::on_pushButton_2_clicked()
{
         thread->quit();
         time->set_flag(0);
         thread->wait();
}

输出:


六、connect最后一个参数

表示连接类型,默认是自动连接。有如下几种选择:

Qt::AutoConnection (0)

        这是默认的连接类型。Qt会根据信号发射者接收者(slot所在的对象)是否位于发射信号的线程中,自动选择使用Qt::DirectConnectionQt::QueuedConnection。如果接收者和发射者位于同一线程,则使用Qt::DirectConnection;否则,使用Qt::QueuedConnection

Qt::DirectConnection (1)

        当信号被发射时,槽函数会立即在同一线程中被调用。这种连接类型适用于槽函数不需要跨线程执行的场景,因为它避免了线程切换的开销。

Qt::QueuedConnection (2)

        信号被发射后,槽函数的调用会被排入接收者对象所在线程的事件循环中。这意味着槽函数将在接收者对象的线程中执行,但不一定是在信号发射后立即执行。这种连接类型适用于跨线程通信,确保了线程安全。

Qt::BlockingQueuedConnection (3)

        这种连接类型与Qt::QueuedConnection类似,但有一个关键区别:发射信号的线程会在槽函数执行完毕并返回之前被阻塞。这意呀着,如果接收者(槽函数)位于发射信号的同一线程中,使用这种连接类型将导致死锁,因为发射信号的线程会等待它自己来完成槽函数的执行。

Qt::UniqueConnection (0x80)

        这不是一个独立的连接类型,而是一个可以与上述任何连接类型通过位或(bitwise OR)操作组合使用的标志。当设置了Qt::UniqueConnection时,如果指定的信号和槽之间的连接已经存在,QObject::connect()函数将不会建立新的连接,并返回false。这有助于避免重复连接同一信号到同一槽,尤其是在动态连接信号和槽时非常有用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LLM】一文学会SPPO

博客昵称&#xff1a;沈小农学编程 作者简介&#xff1a;一名在读硕士&#xff0c;定期更新相关算法面试题&#xff0c;欢迎关注小弟&#xff01; PS&#xff1a;哈喽&#xff01;各位CSDN的uu们&#xff0c;我是你的小弟沈小农&#xff0c;希望我的文章能帮助到你。欢迎大家在…

Vue3-后台管理系统

目录 一、完成项目历程 1、构建项目 2、项目的自定义选项 3、 封装组件 4、配置对应页面的路由 5、从后端调接口的方式 二、引入Element Plus、Echarts、国际化组件 1、Element Plus安装 2、Echarts安装 3、国际化 三、介绍项目以及展示 1、项目是基于Vue3、Element …

C0030.Clion中运行提示Process finished with exit code -1073741515 (0xC0000135)解决办法

1.错误提示 2.解决办法 添加环境变量完成之后&#xff0c;重启Clion软件&#xff0c;然后就可以正常调用由mingw编译的opencv库了。

【es6进阶】vue3中的数据劫持的最新实现方案的proxy的详解

vuejs中实现数据的劫持,v2中使用的是Object.defineProperty()来实现的&#xff0c;在大版本v3中彻底重写了这部分&#xff0c;使用了proxy这个数据代理的方式&#xff0c;来修复了v2中对数组和对象的劫持的遗留问题。 proxy是什么 Proxy 用于修改某些操作的默认行为&#xff0…

Python浪漫之画明亮的月亮

目录 1、效果展示 2、完整版代码 1、效果展示 2、完整版代码 import turtledef draw_moon():# 设置画布turtle.bgcolor("black") # 背景颜色为黑色turtle.speed(10) # 设置绘制速度# 绘制月亮的外圈turtle.penup()turtle.goto(0, -100) # 移动到起始…

《线性代数的本质》

之前收藏的一门课&#xff0c;刚好期末复习&#xff0c;顺便看一看哈哈 课程链接&#xff1a;【线性代数的本质】合集-转载于3Blue1Brown官方双语】 向量究竟是什么 线性代数中最基础、最根源的组成部分就是向量&#xff0c;需要先明白什么是向量 不同专业对向量的看法 物理专…

鸿蒙系统ubuntu开发环境搭建

在RISC-V等平台移植鸿蒙系统OpenHarmony&#xff0c;需要使用linux环境进行代码的编译&#xff0c;为兼顾日常办公需要&#xff0c;可采用WindowsUbuntu虚拟机的混合开发的环境&#xff0c;通过网络及文件夹共享&#xff0c;在主机和虚拟机之间共享文件数据。 工具准备&#x…

智能停车解决方案之停车场室内导航系统(二):核心技术与系统架构构建

hello~这里是维小帮&#xff0c;如有项目需求和技术交流欢迎大家私聊我们&#xff01;点击文章最下方获取智慧停车场方案~撒花&#xff01; 随着城市化进程的加速&#xff0c;停车难问题日益凸显。智能停车系统作为缓解停车压力的有效手段&#xff0c;其核心技术与架构的构建至…

(免费送源码)计算机毕业设计原创定制:Java+JSP+HTML+JQUERY+AJAX+MySQL springboot计算机类专业考研学习网站管理系统

摘 要 大数据时代下&#xff0c;数据呈爆炸式地增长。为了迎合信息化时代的潮流和信息化安全的要求&#xff0c;利用互联网服务于其他行业&#xff0c;促进生产&#xff0c;已经是成为一种势不可挡的趋势。在大学生在线计算机类专业考研学习网站管理的要求下&#xff0c;开发一…

IDEA2023版本中如何启动项目的多个实例

假设现在要启动多个服务&#xff0c;例如简单的客户端和服务端&#xff0c;默认的idea是只能启动一个的&#xff0c;那么我们需要进行配置允许多个项目的同时启动&#xff0c;现在进行多实例的配置。 第一步 点击Edit Configurations 第二步 点击Modify options 第三步 勾选…

图的邻接矩阵和邻接表存储

目录 邻接矩阵存储法 简介 ​编辑 邻接矩阵举例 无向图邻接矩阵 有向图邻接矩阵 当各条边带有权值时 邻接矩阵算法实现 结构体定义和函数声明 函数的实现 邻接表存储法 简介 邻接表的算法实现 结构体定义和函数声明 函数的实现 邻接矩阵和邻接表的差别 邻接矩阵存…

【Linux命令】grep

Linux命令-grep GREP命令&#xff1a;进行字符串数据的比对&#xff0c;并将符合指定模式的字符串行打印出来。1.命令介绍基础正则表达式原始文档如下&#xff1a; 2.练习题&#xff1a;2.1 练习&#xff08;一&#xff09;&#xff1a;2.1.1 读取加行号的文件内容&#xff1a;…

WMS 如何实现智能仓储与自动化物流的无缝对接

【大家好&#xff0c;我是唐Sun&#xff0c;唐Sun的唐&#xff0c;唐Sun的Sun。】 在当今高度竞争的商业环境中&#xff0c;企业对于物流效率和仓储管理的要求日益严苛。智能仓储和自动化物流作为现代物流领域的重要发展方向&#xff0c;能够显著提高物流运作的速度、准确性和成…

DevOps-Jenkins-新手入门级

1. Jenkins概述 1. Jenkins是一个开源持续集成的工具&#xff0c;是由JAVA开发而成 2. Jenkins是一个调度平台&#xff0c;本身不处理任何事情&#xff0c;调用插件来完成所有的工作 1.1 什么是代码部署 代码发布/部署>开发书写的程序代码---->部署测试/生产环境 web服务…

WEB APIS(DOM对象,操作元素内容,属性,表单属性,自定义属性,定时器)

js基础基本语法&#xff1a; 变量&#xff0c;数据类型&#xff0c;循环&#xff0c;函数&#xff0c;对象等(主要是控制台打印&#xff09; WEB APIS 操作DOM BOM &#xff1a; 控制网页元素&#xff0c;交互等各种网页交互效果 js高级 语法&#xff1a; js新增语法&#xff0…

cs144(一)

cs144(一) 1、osi 当应用程序有数据要发送时&#xff0c;应用层将数据交给传输层&#xff0c; 传输层负责将数据可靠或不可靠地传送到另外一端&#xff0c;传输层通过将数据交给网络层来发送数据 网络层负责将数据分成数据包&#xff0c;每个数据包都有正确的目的地址 最后…

IEC61850读服务器目录命令——GetServerDirectory介绍

IEC61850标准中的GetServerDirectory命令是变电站自动化系统中非常重要的一个功能&#xff0c;它主要用于读取服务器的目录信息&#xff0c;特别是服务器的逻辑设备节点&#xff08;LDevice&#xff09;信息。以下是对GetServerDirectory命令的详细介绍。 目录 一、命令功能 …

如何使用AWS Lambda构建一个云端工具(超详细)

首发地址&#xff08;欢迎大家访问&#xff09;&#xff1a;如何使用AWS Lambda构建一个云端工具&#xff08;超详细&#xff09; 1 前言 1.1 无服务器架构 无服务器架构&#xff08;Serverless Computing&#xff09;是一种云计算服务模型&#xff0c;它允许开发者构建和运行…

力扣-位运算-1【算法学习day.41】

前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&am…

MySQL数据库学习(持续更新ing)

1. 什么是数据库&#xff1f;什么是数据库管理系统&#xff1f;什么是SQL&#xff1f;他们之间的关系是什么&#xff1f; 数据库&#xff1a;Database&#xff0c; 简称DB。按照一定格式存储数据&#xff0c;一些文件的组合。 数据库管理系统&#xff1a;DataBaseManagement&…