Bootstrap

【QT】——多线程的使用

目录

基本概念

1.线程类QThread

1.1信号和槽

1.2静态函数

1.3 任务处理函数

2.实例

第一种方式

第二种方式

基本概念

  1. 默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据的更新
  2. 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理
  3. 主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机制 

1.线程类QThread

Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一共提供了两种创建子线程的方式,后边会依次介绍其使用方式

// 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);

1.2信号和槽

// 和调用 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();

1.3静态函数

// 返回一个指向管理当前执行线程的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);	// 单位: 微秒

1.4 任务处理函数

// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();

2.实例

1.点击开始,创建一个生产线程,随机生成10000个数值。

2.通过信号将数组发送给主线程,并显示到窗口上

ui界面:

执行效果:

 

第一种方式

  1. 自定义一个类,并继承 QT 中的线程类 QThread
  2. 在自定义类中重写 run 方法,run函数需要在protected中,在该函数内部编写子线程要处理的具体的业务流程
  3. 在主线程中创建子线程对象
  4. 调用start()方法启动子线程

mythread.hpp        

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>
#include<QThread>
class mythread : public QThread
{
    Q_OBJECT
public:
    explicit mythread(QObject *parent = nullptr);

signals:
     //自定义信号,将QVector<int>发送出去
     void sendVector(QVector<int> v);
public slots:

protected:
    void run(); //线程运行的函数
    int num=10000; 
};

#endif // MYTHREAD_H

 mythread.cpp

#include "mythread.h"
#include<QElapsedTimer>
#include<QVector>
#include<QDebug>
mythread::mythread(QObject *parent) : QThread(parent)
{}

void mythread::run(){
    QElapsedTimer time;
    time.start();
    QVector<int> vector;
    //生成10000个随机数
    for(int i=0;i<num;i++){
        int tmp=rand()%100000;
        vector.push_back(tmp);
    }
    sendVector(vector);

    int curtime=time.elapsed();
    qDebug()<<"生成随机数消耗时间"<<curtime;
}

//QElapsedTimer是一个timer对象,用来统计时间,从start函数开始计时,elapsed返回时间

 mainWindow.cpp

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    mythread* t=new mythread();

    
    //将子线程发送的sendVector信号 给关联起来
    connect(t,&mythread::sendVector,this,[=](QVector<int> v){
        for(int i=0;i<v.size();i++){

            ui->rand_text->appendPlainText(QString::number(v[i]));
        }

    });
    //点击启动按钮,启动线程
    connect(ui->start_button,&QPushButton::clicked,this,[=](){
        //启动线程
        t->start();
    });

}

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

//知识点:
//appendPlainText接口是在文本框中展示数据
//Qstring::number()接口是将 int类型转换为 QString类型

第二种方式

  1. 创建一个任务类task,这个任务类继承QObject
  2. 在任务类中写一个working的函数,函数内部写处理业务的流程
  3. 创建一个QThread 对象 和一个 task任务类对象
  4. 让task对象 使用函数 moveToThread 指定 线程 执行该 任务
  5. 通过信号或者某种方式将 task对象中 working运行
  6. 调用线程start函数,则 线程开始执行任务类中的 working函数。

task.h文件

#ifndef TASK_H
#define TASK_H
#include<QObject>
#include<QVector>
class task:public QObject
{
    Q_OBJECT
 signals:
    void sendVector(QVector<int> v);
    void begin(int num);
public:
    task();
    ~task();
    void working(int num);
};

#endif // TASK_H

task.cpp

#include "task.h"
#include<QElapsedTimer>
#include<QDebug>
task::task()
{}

task::~task(){
}

void task::working(int num){
    QElapsedTimer time;
    time.start();
    //生成10000个随机数
    QVector<int> vector;
    for(int i=0;i<num;i++){
        int tmp=rand()%100000;
        vector.push_back(tmp);
    }
    sendVector(vector);

    int curtime=time.elapsed();
    qDebug()<<"生成随机数消耗时间"<<curtime;
}

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QThread>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QThread* t=new QThread;
    task* t1=new task;
    
    //将任务绑定到线程t上
    t1->moveToThread(t);
    
    connect(t1,&task::sendVector,this,[=](QVector<int> v){
        for(int i=0;i<v.size();i++){
            ui->rand_text_3->appendPlainText(QString::number(v[i]));
        }

    });

    connect(t1,&task::begin,t1,&task::working);
    //点击启动按钮,启动线程
    connect(ui->start_button_3,&QPushButton::clicked,this,[=](){
        //启动线程
        emit t1->begin(10000);//触发working流程开始执行
        t->start();//触发线程开始执行
    });

}

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

注意:使用第二种方式绑定,不能对 task对象 进行父类绑定,如果对父类绑定,则子线程不能运行该任务。

第二种方式更灵活,更加容易维护。

 运行过程中出现的问题:

 如果上面这种情况,则需要在main函数的最前面加上 qRegisterMetaType 对象,声明某种类型是可以通过 信号传递给槽函数。

格式: qRegisterMetaType<类型.("类型")

;