Qt的多线程编程

这篇具有很好参考价值的文章主要介绍了Qt的多线程编程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Qt线程

基本概念

并发

当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其他线程处于挂起状态。虽然看起来所有线程都是一起执行的,但是其实每个时间只有一个线程在执行,这种方式我们成为并发。

并行

当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行。因此,多核CPU可以同时执行多个进程。

为什么需要使用线程

例:给定需求计算一个复杂数据处理所花费的时间。我们有一个start按钮,和一个LCD显示时间,点击按钮开始计时并进行数据处理。

我们创建一个定时器。在start的槽函数中启动定时器,并进行数据处理,数据处理利用QThread::sleep(5)来模拟。定时器每次触发timeout信号,更新LCD数字。代码如下:

//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTimer>
#include <QThread>

//利用定时器计算复杂计算花费的时间
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    QTimer *_timer = new QTimer(this);
};

#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"

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

    //只要定时器启动,自动触发timeout信号,每次触发timeout信号更新LCD显示
    connect(_timer, &QTimer::timeout, [=]() {
       static int i = 0;
       i++;
       ui->lcdNumber->display(i);
    });
}

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

void Widget::on_pushButton_clicked() {
    //如果定时器没有工作才启动
    if (_timer->isActive() == false) {
        _timer->start(100);
    }

    //非常复杂的数据处理,需要花费5s
    QThread::sleep(5);
}

//widget.ui

Qt的多线程编程

运行结果发现,当数据处理结束5秒后,才开始计时。这样我们就不能计算数据处理所花费的时间了。这是为什么呢?

因为这是个单线程的程序,定时器计时和数据处理不是并行处理,而是数据处理结束之后才开始定时器计时。而如果我们使用多线程,则这两个任务可以同时执行,我们就可以得到数据处理所耗费的时间了。

使用多线程的好处

  • 使用多线程可以把占据长时间的任务放到后台去处理
  • 用户界面可以更加吸引人,例如用户点击了一个按键去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
  • 程序的运行可能加快。
  • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等了。

什么时候该使用多线程

大多情况下,要用到多线程的主要是需要处理大量的IO操作时或处理的情况需要花费大量的时间等等。例如:

  • 读写文件
  • 视屏图像的采集、处理、显示、保存等

Qt4实现多线程的方法

Qt4实现多线程的方法

1.自定义一个线程类,继承于QThread

class MyThread : public QThread {
public:
    void run(); //线程处理函数(和主线程不在同一个线程,可以同时处理)

signals:
    void isDone(); //线程处理数据结束发送这个信号
}

2.主线程中启动子线程,开始并行处理。注意,主线程不能直接调用子线程的run函数,而需要通过子线程类的对象调用start()函数间接低矮用run()函数。

//主线程类中
MyThread *_thread = new MyThread(this);
_thread->start(); //通过子线程对象调用start间接调用子线程的run()函数

子线程的创建

还是以刚才的背景为例,我们准备求得复杂信号处理的时长。我们县创建一个子线程类MyThread.

注意以下几点:

  • void run()函数是protected继承的QThread的虚函数,它是线程处理函数实现的入口
  • run()函数不能直接调用,必须通过MyThread类实例化对象调用start()间接调用。
//MyThread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

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

signals:
    void isDone();

protected:
    //QThread的虚函数
    //线程处理函数
    //不能直接调用,通过start()间接调用
    void run(); //线程处理实现入口

public slots:
};

#endif // MYTHREAD_H
//MyThread.cpp
#include "mythread.h"

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

}

void MyThread::run() {
    //复杂的数据处理,用sleep来模拟,大概耗费5s
    sleep(5);

    //处理结束,发送处理完成信号
    emit isDone();
}

主线程中调用子线程

注意:

  • 线程号是有限的,空闲的线程也会占用资源,我们使用完线程后也要将线程关闭。
  • 线程关闭可以用quit()函数来关闭,quit()会等待线程执行完当前工作再关闭线程
  • 关闭完线程之后,需要回收线程资源,**通过wait()来回收线程资源。**类似于Linux回收进程资源的wait和waitpid。
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTimer>
#include <QThread>
#include <mythread.h>

//需求,点击start按钮,显示定时器时间
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

public slots:
    void DealIsDone();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    QTimer *_timer = new QTimer(this);
    MyThread *_thread = new MyThread(this);
};

#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"

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

    //只要定时器启动,自动触发timeout信号,每次触发timeout信号更新LCD显示
    connect(_timer, &QTimer::timeout, [=]() {
       static int i = 0;
       i++;
       ui->lcdNumber->display(i);
    });

    //子线程数据处理结束会发送isDone信号,这时我们暂停定时器.
    connect(_thread, &MyThread::isDone, this, &Widget::DealIsDone);

}

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

//由于线程号是有限的,线程用完之后要将其关闭
void Widget::DealIsDone() {
    _timer->stop();
    //停止线程
    _thread->quit();
    //回收子进程资源
    _thread->wait();
}

void Widget::on_pushButton_clicked() {
    //如果定时器没有工作才启动
    if (_timer->isActive() == false) {
        _timer->start(100);
    }

    //启动子线程同时进行数据处理
    _thread->start();
}

这样,我们就可以LCD显示和数据处理同时进行,这样就可以知道数据处理的时间了。

Qt的多线程编程

Qt5实现多线程的方法

Qt5实现多线程的方法

例:背景是,我们将定时器写成子线程,然后主线程就用来显示LCD的值。

1.设定一个类,继承于QObject

2.类中设置一个线程函数(只有一个线程函数)

//MyThread
class MyThread : public QObject {
public:
    void myTimer() {
        while (1) {
            emit mySignal();
        }
    }
signals:
    void mySignal(); //类似定时器的timeout信号
    void startThread(); //启动线程信号
}

3.主线程中创建线程对象(不能指定父对象)

4.主线程中创建一个QThread子线程对象

5.把自定义线程类加入到子线程

6.启动子线程,并发射启动子线程信号

7.自定义线程函数通过与发射的子线程信号连接来实现子线程。(不能直接调用线程处理函数,直接调用线程处理函数,会导致线程处理函数和主线程在同一个线程,只能通过信号和槽的方式来调用)

子线程的创建

//mythread.h
#define MYTHREAD_H

#include <QObject>
#include <QThread>
#include <QDebug>

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

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

    void setFlag(bool flag);
signals:
    void myTimeOut();
public slots:

private:
    bool _isStop = false;
};

#endif // MYTHREAD_H
//myThread.cpp
#include "mythread.h"

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

}

void MyThread::dealTimeOut() {
    while (!_isStop) {
        //每隔1s发送一个信号
        QThread::sleep(1);
        emit myTimeOut();
        qDebug() << "子线程号:" << QThread::currentThread() << endl;
    }
}

void MyThread::setFlag(bool flag) {
    _isStop = flag;
}

主线程调用子线程

有以下注意事项:

  1. 分别创建子线程对象和自定义线程对象。注意自定义线程对象不能指定父类。
  2. 将自定义线程对象通过moveToThread函数移动到子线程对象中。
  3. 不能直接调用自定义线程运行函数,要调用自定义线程处理函数时,采用以下步骤:
    • 启动子线程
    • 主窗口发送启动线程的信号
    • 自定义线程要执行的处理函数作为槽函数被调用。
  4. 线程结束时,也要进行如下步骤:
    • 子线程调用quit()函数结束线程
    • 子线程调用wait回收子线程资源
    • 注意子线程未结束直接关闭主线程,子线程不会关闭,因此关闭主线程时也要把子线程关闭,并且由于自定义线程没有指定父对象,关闭时也要把自定义线程对象delete掉。
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

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

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

     void dealSignal();

private slots:
    void on_pushButtonStart_clicked();

    void on_pushButtonStop_clicked();

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

private:
    Ui::Widget *ui;
    //动态分配空间不能指定父对象
    MyThread *_myThread = new MyThread;

    //创建子线程,指定父对象
    QThread *_thread = new QThread(this);
};

#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"

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

    //把自定义线程加入到子线程中
    _myThread->moveToThread(_thread);

    connect(this, &Widget::startThread, _myThread, &MyThread::dealTimeOut);
    connect(_myThread, &MyThread::myTimeOut, this, &Widget::dealSignal);
    connect(this, &Widget::destroyed, this, &Widget::on_pushButtonStop_clicked);
    qDebug() << "主线程号:" << QThread::currentThread() << endl;

}

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

void Widget::dealSignal() {
    static int i = 0;
    ui->lcdNumber->display(i++);
}

void Widget::on_pushButtonStart_clicked() {
    //启动线程,但没有启动线程处理函数
    _thread->start();
    _myThread->setFlag(false);
    //不能直接调用线程处理函数
    //直接调用,导致线程处理函数和主线程在同一个线程
//    _myThread->dealTimeOut();
    //只能通过信号与槽的方式来调用
    emit startThread();
}

void Widget::on_pushButtonStop_clicked() {
    _myThread->setFlag(true);
    _thread->quit();
    _thread->wait();
}

connect()的第五个参数的作用

connect()第五个参数的作用,连接方式:默认,队列,直接,第五个参数只有在多线程时才有意义。

  • 如果是多线程,默认使用队列方式
  • 如果是单线程,默认使用直接方式
  • 队列:槽函数所在的线程和接受者一样。
  • 直接:槽函数所在线程和发送者一样。

线程画图

子线程不能操作图形界面,主要用于后台运行,进行数据处理使用。

背景:子线程用于画图,主线程每次点击画图按钮,更新子线程画的图形

子线程的创建

子线程画完图之后,发射更新图片信号,将画好的图片通过信号传送出去。

//mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>
#include <QPainter>
#include <QImage>
#include <QBrush>

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

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

signals:
    void updateImage(QImage img);

public slots:

};

#endif // MYTHREAD_H
//mythread.cpp
#include "mythread.h"

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

}

//线程处理函数
void MyThread::drawImage() {
    //定义QImage绘图设备
    QImage image(500, 500, QImage::Format_ARGB32);
    //定义画家,指定绘图设备
    QPainter painter(&image);

    //定义画笔对象
    QPen pen;
    pen.setWidth(5);
    painter.setPen(pen);

    //定义画刷对象
    QBrush brush;
    brush.setColor(Qt::blue);
    brush.setStyle(Qt::Dense1Pattern);
    painter.setBrush(brush);

    //定义5个点
    QPoint a[5] {
        QPoint(qrand() % 500, qrand() % 500),
        QPoint(qrand() % 500, qrand() % 500),
        QPoint(qrand() % 500, qrand() % 500),
        QPoint(qrand() % 500, qrand() % 500),
        QPoint(qrand() % 500, qrand() % 500)
    };


    painter.drawPolygon(a, 5);

    //通过信号发送图片
    emit updateImage(image);

}

主线程调用子线程

主线程调用子线程步骤如下:文章来源地址https://www.toymoban.com/news/detail-480572.html

  1. 创建子线程对象和自定义函数处理对象。并将自定义函数处理对象moveToThread到创建出的子线程中。
  2. 开启子线程,_thread->start(),并通过connect连接线程处理函数。
  3. 子线程处理函数处理结束后,发送处理结束信号updateImage,主线程接受这个信号,并将接收到的图片画到设备上。
  4. 关闭子线程。
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <mythread.h>
#include <QThread>

namespace Ui {
class Widget;
}

class Widget : public QWidget {
    Q_OBJECT

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

    //重写绘图事件
    void paintEvent(QPaintEvent *event);

public slots:
    void slotGetImage(QImage img);

    void slotClose(); //窗口关闭槽函数

private:
    Ui::Widget *ui;
    QImage _image;
    MyThread *_myThread = new MyThread;
    QThread *_thread = new QThread(this);
};

#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"

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

    //将自定义模块添加到子线程
    _myThread->moveToThread(_thread);

    //启动子线程,但是并没有启动线程处理函数
    _thread->start();

    //线程处理函数,必须通过signal-slot调用
    //drawImage槽函数和接受者_myThread在同一个子线程,而_myThread在子线程_thread中,因此就连接到了子线程。
    connect(ui->pushButton, &QPushButton::clicked, _myThread, &MyThread::drawImage);
    connect(_myThread, &MyThread::updateImage, this, &Widget::slotGetImage);
    connect(this, &Widget::destroyed, this, &Widget::slotClose);
}

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

void Widget::paintEvent(QPaintEvent *event) {
    QPainter painter(this); //指定绘图设备为窗口
    painter.drawImage(50, 50, _image);
}

void Widget::slotGetImage(QImage img) {
    _image = img;
    update(); //更新窗口,间接调用paintEvent
}

void Widget::slotClose() {
    //结束线程
    _thread->quit();
    //回收线程资源
    _thread->wait();
    //销毁自定义线程类对象
    delete _myThread;
}

到了这里,关于Qt的多线程编程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++并发多线程--多个线程的数据共享和保护

    目录 1--创建并等待多个线程 2--数据共享 2-1--数据只读 2-2--数据读写 2-3--共享数据保护简单案例         创建多个线程时,可以使用同一个线程入口函数;         多个线程的执行顺序与操作系统的调度机制有关,并不和创建线程的先后顺序相同;         一般会将多个

    2024年02月12日
    浏览(40)
  • selenium并发处理多个窗口线程/进程任务

    这里以百度搜索为例,通过不同的浏览器来启动不同的线程。

    2024年01月20日
    浏览(42)
  • “深入理解Java的多线程编程“

    多线程编程是指在一个程序中同时运行多个线程,以提高程序的并发性和性能。Java是一门支持多线程编程的强大编程语言,提供了丰富的多线程相关类和接口。 在Java中,可以通过以下方式实现多线程编程: 继承Thread类:创建一个继承自Thread类的子类,并重写run()方法,在

    2024年02月13日
    浏览(67)
  • C# 中的多线程和异步编程

    最近在看代码的过程中,发现有很多地方涉及到多线程、异步编程,这是比较重要且常用的知识点,而本人在这方面还理解尚浅,因此开始全面学习C#中的多线程和异步编程,文中部分内容摘抄自一位前辈的网站:网址链接,为了更便于理解和学习,本人还在个别地方做了一

    2023年04月08日
    浏览(50)
  • Qt QQueue 安全的多线程队列、阻塞队列

    在C++中,queue是一个模板类,用于实现队列数据结构,遵循先进先出的原则。 ♦ 常用方法: · ♦ 简单使用: · ♦ 打印: · QQueue 继承与 QList ♦ 常用方法: · ♦ 实例: · ♦ 打印: · 在多线程编程中,由于QQueue并不是线程安全的,因此我们需要先使用互斥锁(QMutex)来保

    2024年02月16日
    浏览(39)
  • 【基于Qt和OpenCV的多线程图像识别应用】

    这是一个简单的小项目,使用Qt和OpenCV构建的多线程图像识别应用程序,旨在识别图像中的人脸并将结果保存到不同的文件夹中。这个项目结合了图像处理、多线程编程和用户界面设计。 用户可以通过界面选择要识别的文件夹和保存结果的文件夹。然后,启动识别进程。图像

    2024年02月05日
    浏览(40)
  • PyQt应用程序中的多线程:使用Qt还是Python线程?

    多线程模块能够更加高效得完成任务,但是在PyQt 应用程序中实现多线程可以使用 Qt 的线程模块(QThread)或者 Python 的 threading 模块。两者各有优劣,具体选择取决于项目需求和个人偏好。下面我们将以案例来说明两种模块具体得优缺点。 1、问题背景 在 PyQt 应用程序中,编

    2024年02月22日
    浏览(55)
  • 【QT进阶】Qt线程与并发之线程和并发的简单介绍

    往期回顾: 【QT进阶】Qt http编程之实现websocket server服务器端-CSDN博客 【QT进阶】Qt http编程之实现websocket client客户端-CSDN博客 【QT进阶】Qt线程与并发之创建线程的三种方法(超详细介绍)-CSDN博客 主要是做一个简单的补充说明 1、线程 线程是程序执行的 最小单元 ,是操作系统

    2024年04月27日
    浏览(40)
  • Python小姿势 - Python的多线程编程

    Python的多线程编程 Python的多线程编程提供了一个非常简单的方法来让一个Python程序同时运行多个任务。这个方法通过创建新的线程来实现,线程可以被视为一个单独的执行流程。 为了创建一个新线程,我们需要使用Python的_thread模块中的start_new_thread()函数。它需要两个参数:

    2024年02月04日
    浏览(38)
  • Android中的多线程编程与异步处理

    在移动应用开发中,用户体验是至关重要的。一个流畅、高效的应用能够吸引用户并提升用户满意度。然而,移动应用面临着处理复杂业务逻辑、响应用户输入、处理网络请求等多个任务的挑战。为了确保应用的性能和用户体验,多线程编程和异步处理成为了不可或缺的技术

    2024年02月11日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包