Qt-多线程

news2024/11/27 1:15:10

1. Qt 多线程概述

  • Qt 默认只有一个主线程,该线程既要处理窗口移动,又要处理各种运算。

  • 当要处理的运算比较复杂时,窗口就无法移动,所以在处理程序时在很多情况下都需要使用多线程来完成。

示例:移动窗口和复杂循环  

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
  ui->setupUi(this);
  
  connect(ui->startBtn, &QPushButton::clicked, [=](){
  	// 模拟复杂运算
    for (int i = 1; i <= 10000000; i++)
    {
      ui->lcdNumber->display(QString::number(i));
    }
  });
}

2. QThread

QThread 类用来创建子线程

2.1 用法-1

方式: 单独定义线程类,线程类完成全部的业务逻辑,主线程只负责启动子线程和退出子线程

使用步骤:

  1. 创建一个类(MyThread),并继承 QThread

  2. QThread 中有一个 protected 级别的 virtual void run(), 必须在该函数中实现线程业务逻辑

  3. 启动线程必须使用 start() 槽函数

  4. 在 MyThread 中可以定义线程执行完成信号(isDone), 在 run函数结束前发射isDone信号,来处理线程执行完毕的逻辑

示例:

1.创建一个类,并继承 QThread

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);

protected:
    // 线程处理函数,不能直接调用,需要使用start来启动线程
    void run();

signals:
    // 自定义信号,当线程执行完毕时可以发射该信号,告诉系统线程已执行完毕
    void isDone();

public slots:
};

 2.在 run 函数中实现业务逻辑

void MyThread::run()
{
  // 模拟复杂业务逻辑
  for (int i = 0; i < 10000000; i++)
  {
    qDebug() << i;
  }
  
  // 线程结束时发送 isDone 信号
  emit this->isDone();
}

 3.在主窗口实例化 MyThread 类,并调用 start方法,来启动线程

4.链接myThread 对象 和 isFinished 信号

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

  myThread = new MyThread(this);
  connect(ui->startBtn, &QPushButton::clicked, [=](){
    // 通过 start 来启动线程,不能直接调用 run
    myThread->start();
  });

  // 当子线程结束时发送 isDone 信号,连接信号和槽 可以处理子线程结束之后的事情
  connect(myThread, &MyThread::isDone, [=](){
    qDebug() << "线程结束";
    myThread->quit();
  });

}

2.2 用法-2

方式:主窗口与线程类配合完成业务逻辑。

  • 子类提供线程能力,

  • 主窗口利用子类提供的线程能力进行业务处理。 同时还需要开启和关闭子线程

示例: 计时器(线程版)

实现思路: 子类每秒向主窗口发送一个信号,主窗口接收到信号后进行累加操作,并将累加结果显示到窗口中

// mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>

class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);

void sendMsg();

signals:
    void mySignal();
public slots:
};

#endif // MYTHREAD_H

// mythread.c

#include "mythread.h"
#include <QThread>


//核心功能: 没个一段时间主动向主窗口发送一个信号


MyThread::MyThread(QObject *parent) : QObject(parent)
{

}
void MyThread::sendMsg()
{
    while (true) {
        QThread::sleep(1);
        emit this->mySignal();
    }
}


//widget.h

#ifndef WIDGET_H
#define WIDGET_H

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

class Widget : public QWidget
{
    Q_OBJECT

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

signals:
    void startSibThread();

private:
    Ui::Widget *ui;

    MyThread *myThread;

    int num;
};

#endif // WIDGET_H



//widget.c

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QThread>
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    this->myThread = new MyThread;
     QThread *thread  =  new QThread(this);
     this->myThread->moveToThread(thread);


     connect(this->myThread,&MyThread::mySignal, [this](){
        this->num++;
         ui->lcdNumber->display(this->num);
    });

     connect(this, &Widget::startSibThread,this->myThread, &MyThread::sendMsg);

    connect(ui->pushButton,&QPushButton::clicked,[=](){

        thread->start();
        emit this->startSibThread();
    });

}

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

实现步骤:

1.创建子类(MyThread),继承于 QObject。

==在该类中设置一个能够每秒钟发送一次信号的函数, 功能就是提供线程能力==

2.在主窗口中引入MyThread,并将其对象放在线程(QTread)中进行调用 ( 注意:此步并没有启动线程)

3.主窗口中接收信号,在匹配的槽函数中进行业务处理

4.点击按钮时启动线程 ( 使用该方法启动线程时,必须使用信号和槽的方式 )

5.关闭定时器: ① 结束线程 ② 结束 while 循环

        1.创建子类(MyThread),继承于 QObject (在该类中设置一个能够每秒钟发送一次信号的函数)

class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);

    // 线程处理函数
    void dealThread();

signals:
    // 提供给主窗口的信号
    void mySignal();

public slots:
};
void MyThread::dealThread()
{
    while (1)
    {
        QThread::sleep(1);
        emit this->mySignal();
        qDebug() << "子线程号:" << QThread::currentThread();
    }
}

2.在主窗口中引入MyThread,并将其对象放在线程中进行调用

3.主窗口中接收信号,在匹配的槽函数中进行业务处理

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

  // 实例化自定义的线程,但是不能指定父对象
  myThread = new MyThread;

  // 创建子线程
  qThread = new QThread(this);

  // 将自定义的线程加入到子线程中, 就是将自定义线程移动到系统提供的线程对象中
  // 如果实例化 MyThread 时指定了父对象就不能移动了
  myThread->moveToThread(qThread);

  // 处理线程发射的信号
  connect(myThread, &MyThread::mySignal, [=](){
    static int i = 0;
    i++;
    ui->lcdNumber->display(QString::number(i));
  });
}

4.点击按钮时启动程序

该方式启动线程时需要调用 start() 方法

同时发送自定义信号 startSubThread

void Widget::on_startBtn_clicked()
{
    // 启动线程
    qThread->start();

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

    ......
    myThread->moveToThread(qThread);

    // 处理线程发射的信号
    connect(myThread, &MyThread::mySignal, [=](){
        static int i = 0;
        i++;
        ui->lcdNumber->display(QString::number(i));
    });

    qDebug() << "主线程号:" << QThread::currentThread();

    // 在 startSubThread 信号中调用子类提供的信号
    connect(this, &Widget::startSubThread, myThread, &MyThread::dealThread);

}

5.关闭定时器:① 结束线程 ② 结束 while 循环

在 MyThread 中设置 isStop 属性,用来控制线程和循环的开启或者关闭

class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);

    // 线程处理函数
    void dealThread();

    // 修改isStop状态
    void setIsStop(bool flag);

signals:
    // 提供给主窗口的信号
    void mySignal();

public slots:

private:
  	// 是否关闭线程的标志   true 为关闭, false 为不关闭
    bool isStop = false;
};
void MyThread::setIsStop(bool flag)
{
    this->isStop = flag;
}
void Widget::on_stopBtn_clicked()
{
    myThread->setIsStop(true);
    qThread->quit();
    qThread->wait();
}

用法小结:

  1. 创建 MyThread 类(继承QObject即可)

① 提供一个信号(void mySignal())

② 定义了一个方法(void dealThread())

核心功能:使用死循环,每隔一段时间之后就发射一个信号 (角色:监工)

  1. 在窗口类中(widget)

① 实例化 MyThread 得到 myThread对象

② 监听 mySignal 信号,一旦监听到信号之后就去实现业务的主体功能(i++,lcd->display(i))

③ 实例化 QThread 得到 qThread对象,再将 myThread转移到qThread中,这个时候就有了线程的功能

④ 在开始按钮上绑定信号和槽,在槽函数中调用 qThread->start() 来启动线程

⑤ 调用 MyThread::dealThread 方法,必须要在 Widget 类中使用自定义信号(startSubThread)的方式,并在其处理信号的槽函数位置调用 MyThread::dealThread 方法

2.3 轮播图

绘图基础回顾:

  1. 绘制图片要在 void paintEvent(QPaintEvent *event) 事件中

  2. 使用到的类 QPainter 、 QPixmap

  3. 窗口中的图片需要进行重绘时,需要使用 QWidget 的 update 方法

第一步: 通过点击信号切换图片

核心思路: 将图片资源地址保存在一个 QVector 中,通过索引号来访问; 切换到下一张图片就是 索引号自增1 ,再调用 update() 方法重绘图片

① 使用数组保存图片资源地址

② 使用索引来控制显示哪张图片

③ 在 paintEvent 事件中绘制图片

④ 点击下一张按钮时对索引号进行自增1,再重绘图片

class Widget : public QWidget
{
  Q_OBJECT

public:
  explicit Widget(QWidget *parent = nullptr);
  ~Widget();
  // 绘图事件
  void paintEvent(QPaintEvent *);

private:
  Ui::Widget *ui;
	// imgPaths 用来保存图片资源路径
  QVector<QString> imgPaths;
  // index 用来控制显示 imPaths 中的那张图片
  int index = 0;
};
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
  ui->setupUi(this);

  this->resize(600, 600);

  // 将数组保存在一个 QVector 中
  imgPaths = {
    ":/images/1.jpg", ":/images/2.jpg", ":/images/3.jpg", ":/images/4.jpg"
  };

  // 点击下一张按钮时对索引号自增1, 注意进行边界检测
  connect(ui->nextBtn, &QPushButton::clicked, [=](){
    this->index = ++this->index == 4 ? 0 : this->index;
    // 重绘图片
    this->update();
  });
}

void Widget::paintEvent(QPaintEvent *)
{
  // 根据索引号绘制图片
  QPainter painter(this);
  QPixmap pix;
  pix.load(this->imgPaths[this->index]);
  painter.drawPixmap(QRect(0, 0, 200, 200), pix);
}

第二步: 使用线程替换 next 按钮

核心思路:创建 MyThread 类,每三秒向主窗口发送一个信号,主窗口每次接收到该信号时自动对索引号自增1,接着在重绘图片

  1. 创建 MyThread 类,每3秒向窗口类

  2. 主窗口监听到MyThread发送的信号后,对索引号进行自增1,接着在重绘图片

  3. 程序启动时,开启子线程

1.创建MyThread 类,每3秒向窗口类

class MyThread : public QObject
{
    Q_OBJECT
public:
  explicit MyThread(QObject *parent = nullptr);

  // 定义线程函数
  void dealThread();

signals:
  // 自定义信号,每三秒向主窗口发送一次信号
  void mySignal();

};
void MyThread::dealThread()
{
  // 每 3 秒向窗口发送一次信号
  while (1)
  {
    QThread::sleep(3);
    emit this->mySignal();
  }
}

2.主窗口监听到MyThread发送的信号后,对索引号进行自增1,接着在重绘图片

class Widget : public QWidget
{
    Q_OBJECT

public:
    ...

signals:
    // 启动子线程的信号
    void startSubThread();

private:
    ...

    MyThread *myThread;
    QThread *qThread;
};
// 实例化自定义类 和 线程类,并将自定义类添加到线程中
myThread = new MyThread;
qThread = new QThread(this);
myThread->moveToThread(qThread);
// 通过线程调整索引号
connect(myThread, &MyThread::mySignal, [=](){
  this->index = ++this->index == 4 ? 0 : this->index;
  this->update();
});

3.程序启动时,开启子线程

// 启动子线程就直接调用 dealThread 槽函数
connect(this, &Widget::startSubThread, myThread, &MyThread::dealThread);

// 启动子线程
qThread->start();
emit this->startSubThread();

2.4 轮播图实现代码(全)

//mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>

class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    void sendMsg();

    void setIsRun(bool flag);
signals:
    void mySignal();
public slots:

private:
    //true 循环发射信号,false 停止循环发送信号
    bool isRun = true;
};

#endif // MYTHREAD_H

//mythread.c

#include "mythread.h"
#include <QThread>

MyThread::MyThread(QObject *parent) : QObject(parent)
{

}
 void MyThread:: sendMsg()
 {
     while(this->isRun)
     {
         QThread::sleep(3);
         emit this->mySignal();
     }
 }
 void MyThread::setIsRun(bool flag)
{
  this->isRun = flag;
 }


//widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QVector>
#include "mythread.h"
#include <QThread>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

    void enterEvent(QEvent *);
    void leaveEvent(QEvent *);

private slots:
    void on_pushButton_2_clicked();

    void on_pushButton_clicked();
signals:
    void startSubThread();
private:
    Ui::Widget *ui;

    MyThread *myThread;
    QThread *qThread;


    QVector<QString>imgList;
    int index = 0;
};

#endif // WIDGET_H


//widget.c

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

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

    this->imgList = {":/images/ashe.png",":/images/jax.png",":/images/vn.png"};

    ui->label->setPixmap(QPixmap(this->imgList[this->index]));
    ui->label->setScaledContents(true);

    //1.准备线程
    this->myThread = new MyThread;
    this->qThread = new QThread;
    this->myThread->moveToThread(this->qThread);

    connect(this,&Widget::startSubThread,this->myThread,&MyThread::sendMsg);
    //3.处理监工提供信号
    connect(this->myThread,&MyThread::mySignal,[this](){
        this->index++;
        if (this->index == this->imgList.size())
        {
            this->index = 0;
        }
        ui->label->setPixmap(QPixmap(this->imgList[this->index]));
        });

    //2. 启动子线程
    this->qThread->start();
    emit this->startSubThread();


}

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

void Widget::on_pushButton_2_clicked()
{
    this->index++;
    if (this->index == this->imgList.size())
    {
        this->index = 0;
    }
    ui->label->setPixmap(QPixmap(this->imgList[this->index]));
}

void Widget::on_pushButton_clicked()
{
    this->index--;
    if (this->index == -1)
    {
        this->index = this->imgList.size() - 1;
    }
    ui->label->setPixmap(QPixmap(this->imgList[this->index]));
}

//鼠标进入停止线程
void Widget::enterEvent(QEvent *)
{
    this->qThread->quit();
    this->myThread->setIsRun(false);

}

//鼠标离开启动线程
void Widget::leaveEvent(QEvent *)
{
    this->myThread->setIsRun(true);
    this->qThread->start();
    emit this->startSubThread();
}

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

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

相关文章

八股面试2(自用)

mysql存储引擎 存储引擎&#xff1a;定义数据的存储方式&#xff0c;以及数据读取的实现逻辑 在以前数据库5.5默认MyISAM引擎&#xff0c;之后默认InnoDB引擎 MyISAM引擎的数据和索引是分开存储的&#xff0c;InnoDb将索引和文件存储在同一个文件。 MyISAM不支持事务&#…

SPOOLing技术详解,结合实际场景让你了解什么是假脱机技术。

SPOOLing技术 ​ 在手工操作阶段&#xff0c;主机直接从I/O设备获取数据&#xff0c;但是由于设备速度很慢&#xff0c;主机速度很快。人机速度矛盾明显&#xff0c;主机需要浪费很多时间来等待设备。 什么是脱机技术&#xff0c;脱机技术可以解决什么问题&#xff1f; 所谓脱…

大数据测试:Charles修改响应数据

上一篇大数据测试&#xff1a;Fiddler修改响应数据-CSDN博客 &#xff0c;有同学反馈有没有Charles的方式修改响应数据&#xff0c;本篇就是Charles修改数据操作步骤&#xff0c;相比较fiddler&#xff0c;Charles相对简单&#xff0c;便捷&#xff0c;我很喜欢 1、背景&…

【笔记】【YOLOv10图像识别】自动识别图片、视频、摄像头、电脑桌面中的花朵学习踩坑

&#xff08;一&#xff09;启动 创建环境python3.9 打开此环境终端 &#xff08;后面的语句操作几乎都在这个终端执行&#xff09; 输入up主提供的语句&#xff1a;pip install -r requirements.txt 1.下载pytorch网络连接超时 pytorch网址&#xff1a; Start Locally | P…

java -jar 命令自动重启 Java 项目

一、java -jar 方式运行项目 重启Java项目通常意味着你需要先停止当前运行的Java进程&#xff0c;然后再次启动它。下面是在CentOS上执行这些步骤的一种常见方法&#xff1a; 停止Java进程 找到Java进程的PID&#xff1a; 使用ps命令配合grep来查找运行中的Java进程的PID&#…

【Java SE 】封装 的特性 和 static 详解

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. 封装的概念 1.1 一个例子 2. 访问权限控制符 3. 包的概念 3.1 import 导入 3.2 常见的包 4. static 静态成员 4.1 static 使用情况 4.2 static 修…

> Invalid revision: 3.22.1-g37088a8-dirty

Android项目使用cmake 3.22.1&#xff0c;编译时报错&#xff1a; > Invalid revision: 3.22.1-g37088a8-dirty解决方法一&#xff1a; 升级Gradle版本和AGP的版本&#xff1b; 建议使用AS推荐的版本&#xff1a; 目前可运行的版本配置&#xff1a; AS&#xff1a;Jel…

champ模型部署指南

一、介绍 champ是由阿里巴巴、复旦大学和南京大学的研究人员共同提出的一种基于3D的将人物图片转换为视频动画的模型&#xff0c;该方法结合了3D参数化模型(特别是SMPL模型)和潜在扩散模型&#xff0c;能够精确地捕捉和再现人体的3D形状和动态&#xff0c;同时保持动画的时间一…

读书读到NOBEL

最近在读陈逸鹤的《程序员的自我修养》这本书&#xff0c;里面有这么一段话&#xff1a; “远古时代的人们只能创造出用于猎捕的长矛&#xff0c;而今天借助来自各行各业人 们的智慧&#xff0c;我们可以制造出高铁、大型飞机&#xff0c;并探索宇宙。但要更进一步解决人类所面…

2024_E_100_连续字母长度

连续字母长度 题目描述 给定一个字符串&#xff0c;只包含大写字母&#xff0c;求在包含同一字母的子串中&#xff0c;长度第 k 长的子串的长度&#xff0c;相同字母只取最长的那个子串。 输入描述 第一行有一个子串(1<长度<100)&#xff0c;只包含大写字母。 第二行为…

GPT-4o canvas不是对cursor的颠覆,而是人与AI交互的新探索

谈一下Openai新发布的canvas。 关于这个产品的介绍不多说了&#xff0c;网上已经有很多&#xff0c;主要谈下我对它以及相似竞品的比较&#xff0c;以及我的一些看法。 1、vs Claude Artifacts&#xff1a;是chatbot编程方面直接竞品&#xff0c;不过现阶段还是有很大的区别。…

二百六十八、Kettle——同步ClickHouse清洗数据到Hive的DWD层静态分区表中(每天一次)

一、目的 实时数仓用的是ClickHouse&#xff0c;为了避免Hive还要清洗数据&#xff0c;因此就直接把ClickHouse中清洗数据同步到Hive中就行 二、所需工具 ClickHouse&#xff1a;clickhouse-client-21.9.5.16 Kettle&#xff1a;kettle9.2 Hadoop&#xff1a;hadoop-3.1.3…

TH-OCR:强大的光学字符识别工具与车牌识别应用

在当今数字化的时代&#xff0c;高效准确地识别文本和图像中的字符变得至关重要。TH-OCR&#xff08;清华 OCR&#xff09;作为一款优秀的光学字符识别软件&#xff0c;以其卓越的性能和广泛的应用场景&#xff0c;受到了众多用户的青睐。其中&#xff0c;车牌识别功能更是在交…

嵌入式入门学习——6Protues点亮数码管,认识位码和段码,分辨共阴还是共阳(数字时钟第一步)

0 系列文章入口 嵌入式入门学习——0快速入门&#xff0c;Let‘s Do It&#xff01; 首先新建基于Arduino UNO的protues工程&#xff0c;见本系列第3篇文章 1 点“P”按钮找器件 2 输入“seg”或“digit”查找数码管器件 3 找到我们想要的6位7段数码管 4如图A、B…DP都是段码…

一、go入门

go入门 Go历史1.1 诞生时间1.2 里程碑1.3 团队核心人员 2. 为什么使用Go3. 安装Go5. 入门案例6. 开发工具 Go历史 1.1 诞生时间 Go 语言起源 2007 年&#xff0c;并于 2009 年正式对外发布。它从 2009 年 9 月 21 日开始作为谷歌公司 20% 兼职项目&#xff0c;即相关员工利用…

MATLAB小波变换图像融合系统

二、应用背景及意义 本课题利用小波变换进行图像的融合&#xff0c;然后对融合的结果进行图像质量的评价。所谓小波变换图像融合就是对多个的信息目标进行一系列的图像提取和合成&#xff0c;进而可以获得对同一个信息目标的更为精确、全面、可靠的高低频图像信息描述。并且也…

Vue2项目-二进制流预览

一、docx文档 软件&#xff1a;docx-preview&#xff1b; 版本&#xff1a;"^0.1.20"&#xff1b; 1、安装docx-preview npm i docx-preview0.1.20 2、组件配置 <template><div ref"wordContainer"></div> </template><s…

java集合进阶篇-《泛型通配符及其练习》

个人主页→VON 收录专栏→java从入门到起飞 目录 一、前言 二、泛型通配符简要概述 基本概念 无界通配符 (?)&#xff1a; 上限通配符 (? extends T)&#xff1a; 下限通配符 (? super T)&#xff1a; 三、思考 四、综合练习 Animal类及其javabeen Cat类 Dog类 H…

04 设计模式-创造型模式-建造者模式

建造者模式是一种创建型设计模式&#xff0c;它允许你创建复杂对象的步骤与表示方式相分离。 建造者模式是一种创建型设计模式&#xff0c;它的主要目的是将一个复杂对象的构建过程与其表示相分离&#xff0c;从而可以创建具有不同表示形式的对象。 设计模式&#xff0c;最近…

1. 安装框架

一、安装 Laravel 11 框架 按照官方文档直接下一步安装即可 1. 安装步骤 2. 执行数据库迁移 在.env文件中提前配置好数据库连接信息 php artisan migrate二、安装 Filament3.2 参考 中文文档 进行安装 1. 安装 拓展包 composer require filament/filament:"^3.2" -W…