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

embedded/2024/11/23 19:12:08/

一、线程

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


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

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

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

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

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

3. 改善程序结构。

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

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


三、QThread类常用的方法

Public FunctionsQThread(QObject *parent = 0) //构造函数  //pthread_createbool isFinished() const  //判断线程是否退出bool	wait(unsigned long time = ULONG_MAX)   //pthread_join(&id)//等待某个线程结束,最多等待time ms,如果时间没有设置,那么永远等待。Public Slotsvoid	start(Priority priority = InheritPriority)  //启动线程必须使用startvoid	terminate()-->杀死线程  //pthread_cancelStatic Public MembersQt::HANDLE	currentThreadId() [static] //得到当前执行者线程ID,可以直接qDebugvoid	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_OBJECTpublic: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_OBJECTpublic: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_OBJECTpublic: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。这有助于避免重复连接同一信号到同一槽,尤其是在动态连接信号和槽时非常有用。


http://www.ppmy.cn/embedded/139918.html

相关文章

界面控件DevExpress WPF中文教程:网格视图数据布局的列和卡片字段

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

redis-击穿、穿透、雪崩

击穿、穿透、雪崩经常听人说吧&#xff1f; 那他到底是啥呢&#xff1f;无非就是在有缓存层的情况下&#xff0c;对各种绕过缓存层从而直接落到了DB上的情况进行的分类。 概念性的东西大概如下&#xff0c;我是记不住&#xff0c;后期具体使用与规避这些问题才是大事&#xff…

Excel表查找与引用函数、逻辑函数、财务函数

一、查找与引用函数 函数说明ADDRESS返回单元格的地址CHOOSE从参数列表中选择一个值COLUMN返回单元格的列号COLUMNS返回数组中列的数量HLOOKUP在表格的首行查找值&#xff0c;并返回指定行的值INDEX返回表或区域中的值INDIRECT根据文本字符串返回引用LOOKUP一维查找函数MATCH返…

数据抓取与存储:将网络爬虫数据保存到数据库的详细指南

在当今信息爆炸的时代&#xff0c;网络爬虫已经成为获取和处理数据的重要工具。将爬取的数据保存到数据库中&#xff0c;不仅可以有效地组织和存储数据&#xff0c;还可以为后续的数据分析和处理提供便利。本文将详细介绍如何将爬取的数据保存到数据库中&#xff0c;包括关系型…

c++ 20 语法规范, vs2019 类 exception 的定义,在 vcruntime_exception .h

这个源头文件&#xff0c;在 STL 模板库里的其他模板的定义中&#xff0c;会引用到&#xff0c;因此&#xff0c;记录与注释一下。源代码很短&#xff0c;直接展开来&#xff0c;同时也附上资源 .h 文件&#xff1a; // // vcruntime_exception.h // // Copyright (c) Mi…

PyCharm的类型警告: Expected type ‘SupportsWrite[bytes]‘, got ‘BinaryIO‘ instead

记录时使用的PyCharm版本: PyCharm 2024.3 (Professional Edition) Build #PY-243.21565.199, built on November 13, 2024 问题描述 当在PyCharm里使用pickle保存文件, 比如以下代码这样: with open(meta_save_path, wb) as f:pickle.dump(meta, f)会发现PyCharm对此发出类型…

为什么芯麦的 GC4931P 可以替代A4931/Allegro 的深度对比介绍

在电机驱动芯片领域&#xff0c;芯麦 GC4931P 和 A4931 都是备受关注的产品。它们在多种应用场景中发挥着关键作用&#xff0c;今天我们就来详细对比一下这两款芯片。 一、性能参数对比 &#xff08;一&#xff09;电流输出能力 A4931 具有一定的电流输出能力&#xff0c;但芯…

JVM(五、垃圾回收器)

经典的垃圾回收器大概有7种&#xff0c;这些收集器的目标、特性、原理、使用场景都有所区别&#xff0c;有的适用于年轻代&#xff0c;有的适用于老年代&#xff0c;图中展示的就是这7中垃圾回收器&#xff0c;如果两个垃圾回收器有连线&#xff0c;则表明可以配合使用。这个关…