Qt 多线程

Qt 多线程


我以前是有学过操作系统的,但是对于多线程依旧处于非常迷茫的阶段,虽然知道多线程是为了程序能并发运行,当时在平常的练习中很少有用到多线程,直接导致不知道如何在实际的场景中运用多线程。不过有幸看到这篇博客(Qt中多线程的使用),让我明白了多线程的运用。

  • 为什么需要使用多线程

正常创建的项目为单线程,一次只能处理一个事件。例如:当前程序正在执行指令A,而这时再给一个指令B,那指令B的执行需要等到指令A结束后才能执行 (程序正在跑循环,这时移动窗口是不会有响应的,但是程序结束后,窗口会弹到之前窗口移动操作结束的位置)。

为了同时可以执行多条指令,就不得不用上多线程。对于以上的情况就需要一个线程处理窗口事件,其他的线程执行逻辑运算,各司其职。

  • Qt 使用多线程需要注意的事项
  • 默认的线程在Qt中称为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据数据更新。
  • 子线程只能负责后台的业务逻辑处理,不能对窗口对象做任何的操作,如果需要对窗口对象做操作,则需要发送操作信号给主线程,让主线程进行操作。
  • 子线程的开始执行需要调用 start() 函数,start() 函数默认调用 run() 函数,千万不可直接调用子线程的 run() 函数,若直接调用子线程的 run() ,这并不会启动子线程,而是将子线程中的 run() 函数让主线程执行。
  • 主线程和子线程之间的通信,可采用 emit 发送信号。

1.线程类 QThread
// QThread 类常用 API
// 构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判断线程中的任务是不是处理完毕了
bool QThread::isFinished() const;
// 判断子线程是不是在执行任务
bool QThread::isRunning() const;

// Qt中的线程可以设置优先级
// 得到当前线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
优先级:
    QThread::IdlePriority         --> 最低的优先级
    QThread::LowestPriority
    QThread::LowPriority
    QThread::NormalPriority
    QThread::HighPriority
    QThread::HighestPriority
    QThread::TimeCriticalPriority --> 最高的优先级
    QThread::InheritPriority      --> 子线程和其父线程的优先级相同, 默认是这个
// 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);
// 调用线程退出函数之后, 线程不会马上退出因为当前任务有可能还没有完成, 调回用这个函数是
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);

// 和调用 exit() 效果是一样的
// 代用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
[slot] void QThread::terminate();

// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();

// 返回一个指向管理当前执行线程的QThread的指针
[static] QThread *QThread::currentThread();
// 返回可以在系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
// 线程休眠函数
[static] void QThread::msleep(unsigned long msecs);	// 单位: 毫秒
[static] void QThread::sleep(unsigned long secs);	// 单位: 秒
[static] void QThread::usleep(unsigned long usecs);	// 单位: 微秒

// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();
/*
这个 run() 函数式虚函数,如果想让创建的子程序执行某个任务,需要写一个子类让其继承 QThread,并且在子类中重写父类的 run() 方法。
 */
2.线程创建

Qt 提供了 QThread 类,通过这个类就能创建子线程。Qt 一共有两种创建子线程的方式,一种是创建 QThread 的子类,让其继承 QThread,再重写父类中的 run() 方法。另一种就是创建一个 QObject 的子类,然后在主线程中再创建一个子线程,最后将 QObeject 的子类对象移动到子线程对象中去。

2.1.1 方法一
  1. 创建 QThread 的子类,继承 QThread.

    class MyThread:public QThread{
        ......
    }
    
  2. 重写父类中的 run() 方法,在函数体内编写子线程要处理的业务流程

    class MyThread:public QThread{
    protected:
        void run(){
            ......
        }
    }
    
  3. 在主线程中创建子线程对象

    MyThread *sub = new MyThread;
    
  4. 启动子线程

    sun->start();
    
2.1.1 示例

在同一个窗口中同时执行两条指令,假设只有一个线程,让其记数,会发现窗口不能更新数字,如果此时用鼠标托动窗口,会出现无反应的情况。

在下面的窗口中,点击按键1,会在按键1的上方出现数字,点击按键2,会在按键2的上方出现数字。

在这里插入图片描述

//mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class mythread : public QThread
{
    Q_OBJECT

public:
    explicit mythread(QObject *parent = 0);
    void run();

signals:
    void Num(int);
};

#endif // MYTHREAD_H

//mythread.cpp

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

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

}

void mythread::run(){
    int num = 0;
    qDebug() << "Thread 开始执行" << QThread::currentThread();
    while(num <= 100) {
        emit Num(num ++);

        QThread::sleep(1);

    }
    qDebug() << "Thread 结束执行";
}
// widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H

// widget.cpp

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

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    qDebug() << "主线程 Thread: " << QThread::currentThread();
    mythread *my = new mythread;
    connect(my,&mythread::Num,this,[=](int num){
        ui->label->setText(QString("%1").arg(num));
    });

    connect(ui->time_btn_1,&QPushButton::clicked,this,[=]{
        my->start();
    });

    mythread *my2 = new mythread; //此处如果采用mythread my2的方式定义会报错。
    connect(my2,&mythread::Num,this,[=](int num){
        ui->label_2->setText(QString("%1").arg(num));
    });
    connect(ui->time_btn_2,&QPushButton::clicked,this,[=]{
        my2->start();
    });
}

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

这种方式创建子线程是非常简单的,但是假设一个子线程需要处理多个任务,那所有的处理逻辑都需要写到 run() 函数中,这样会使得处理逻辑混乱,不利于维护。

2.2.1方法二
  1. 创建 QObject 的子类

    class MyWork:public QObject{
        ......
    }
    
  2. 在子类中添加一个公共的成员函数,函数体内就是需要执行的业务逻辑

    class MyWork:public QObject{
    public:
        void working(){
            ......
        }
    }
    
  3. 在主线程中创建一个 QThread 对象

    QThread *sub = new QThread;
    
  4. 在主线程中创建工作类的对象(不能为创建的对象指定父类)

    MyWork *work = new MyWork(this); //错误,此处指明父类后,直接加入到了主线程中,此处的错误可能会因为编译器版本的不同而出现不同的错误。
    MyWork *work = new MyWork;
    
  5. 将 MyWork 对象移动到创建的子线程对象中,调用 QObject 类提供的 moveToThread() 方法

    work->moveToThread(sub);
    
2.2.2 示例

需求如上

// mywork.h

#ifndef MYWORK_H
#define MYWORK_H

#include <QObject>

class MyWork : public QObject
{
    Q_OBJECT
public:
    explicit MyWork(QObject *parent = nullptr);
    void working();

signals:
    void Num(int);

public slots:
};

#endif // MYWORK_H
// mywork.cpp

#include "mywork.h"
#include <QThread>
#include <QDebug>

MyWork::MyWork(QObject *parent) : QObject(parent)
{

}

void MyWork::working(){
    qDebug() << "子线程开始执行:" << QThread::currentThread();
    int num = 0;
    while(num < 10) {
        emit Num(num ++);
        QThread::msleep(1000);
    }
    qDebug() << "子线程结束!";
}

// widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H
// widget.cpp

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

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    qDebug() << "主线程 Thread: " << QThread::currentThread();
    QThread *sonThread = new QThread;
    QThread *sonThread2 = new QThread;

    MyWork *work = new MyWork;//不可使用全局指针,否则子线程无法启动
    MyWork *work2 = new MyWork;

    work->moveToThread(sonThread);

    work2->moveToThread(sonThread2);
 
    sonThread->start();
    sonThread2->start();
    connect(work,&MyWork::Num,this,[=](int num){
        ui->label->setNum(num);
    });
    connect(work2,&MyWork::Num,this,[=](int num){
        ui->label_2->setNum(num);
    });

    connect(ui->time_btn_1,&QPushButton::clicked,work,&MyWork::working);
    connect(ui->time_btn_2,&QPushButton::clicked,work2,&MyWork::working);
}

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

这种方式便于处理多个不相关的业务流程,可以创建多个类似于 MyWork 的类,将业务流程放在类的公共成员函数中,然后将这个业务类的实例对象移动到对应的子线程中 moveToThread() 就可以了,这样可以让编写的程序更加灵活,可读性更强,更易于维护。

参考博客:Qt中多线程的使用

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt是一个跨平台的应用程序框架,提供了丰富的多线程编程支持。Qt多线程编程主要依靠QThread类和信号槽机制来实现。 QThread类封装了线程的基本操作,使得我们可以通过继承这个类来实现自己的线程。通过重写QThread类中的run()方法,我们可以在这个方法中实现具体的线程操作。 例如,下面是一个简单的QThread类的定义: ```c++ class MyThread : public QThread { Q_OBJECT public: explicit MyThread(QObject *parent = nullptr); protected: void run() override; signals: void resultReady(int result); }; ``` 在这个例中,我们重写了run()方法来实现线程的具体操作。在这个方法中,我们可以调用其他Qt类或者自己实现的函数来完成多线程的任务。另外,我们还定义了一个resultReady信号,用于在线程执行完毕后向主线程发送消息。 接下来,我们可以在主线程中创建一个MyThread对象,并连接它的resultReady信号到一个槽函数中,以便在线程执行完毕后处理结果。例如: ```c++ MyThread *thread = new MyThread(this); connect(thread, &MyThread::resultReady, this, &MyClass::handleResult); thread->start(); ``` 在这个例中,我们创建一个MyThread对象并启动它。在线程执行完毕后,它会发送resultReady信号,我们将这个信号连接到handleResult槽函数中来处理结果。 除了QThread类外,Qt还提供了许多其他的多线程编程工具,如QThreadPool类、QMutex类、QWaitCondition类等,可以帮助我们更方便地实现多线程编程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值